@nextlyhq/plugin-form-builder 0.0.2-alpha.32 → 0.0.2-alpha.34

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.
@@ -1,4 +1,5 @@
1
1
  // src/components/submissions/SubmissionList.tsx
2
+ import { DataTableView } from "@nextlyhq/plugin-sdk/admin";
2
3
  import { useState, useCallback, useMemo } from "react";
3
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
5
  function formatValue(value) {
@@ -18,18 +19,6 @@ function formatDate(date) {
18
19
  const d = typeof date === "string" ? new Date(date) : date;
19
20
  return d.toLocaleString();
20
21
  }
21
- function getStatusBadgeClass(status) {
22
- switch (status) {
23
- case "new":
24
- return "submission-status-badge submission-status-badge--new";
25
- case "read":
26
- return "submission-status-badge submission-status-badge--read";
27
- case "archived":
28
- return "submission-status-badge submission-status-badge--archived";
29
- default:
30
- return "submission-status-badge";
31
- }
32
- }
33
22
  function SubmissionList({
34
23
  form,
35
24
  submissions,
@@ -51,7 +40,6 @@ function SubmissionList({
51
40
  if (filter === "all") return submissions;
52
41
  return submissions.filter((sub) => sub.status === filter);
53
42
  }, [submissions, filter]);
54
- const allSelected = filteredSubmissions.length > 0 && selectedIds.size === filteredSubmissions.length && filteredSubmissions.every((s) => selectedIds.has(s.id));
55
43
  const someSelected = selectedIds.size > 0;
56
44
  const toggleSelect = useCallback((id) => {
57
45
  setSelectedIds((prev) => {
@@ -64,13 +52,6 @@ function SubmissionList({
64
52
  return next;
65
53
  });
66
54
  }, []);
67
- const toggleSelectAll = useCallback(() => {
68
- if (allSelected) {
69
- setSelectedIds(/* @__PURE__ */ new Set());
70
- } else {
71
- setSelectedIds(new Set(filteredSubmissions.map((s) => s.id)));
72
- }
73
- }, [allSelected, filteredSubmissions]);
74
55
  const handleStatusChange = useCallback(
75
56
  async (id, status) => {
76
57
  if (!onStatusChange) return;
@@ -141,12 +122,91 @@ function SubmissionList({
141
122
  },
142
123
  [onBulkStatusChange, selectedIds]
143
124
  );
144
- const handleRowClick = useCallback(
145
- (submission) => {
146
- onViewDetail?.(submission);
147
- },
148
- [onViewDetail]
125
+ const columns = useMemo(() => {
126
+ const base = [
127
+ {
128
+ name: "id",
129
+ header: "ID",
130
+ cell: ({ row }) => /* @__PURE__ */ jsxs("code", { className: "font-mono text-xs text-muted-foreground", children: [
131
+ row.id.slice(0, 8),
132
+ "..."
133
+ ] })
134
+ },
135
+ {
136
+ name: "status",
137
+ header: "Status",
138
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-none border border-border bg-muted px-2 py-0.5 text-[11px] font-medium capitalize text-foreground", children: row.status })
139
+ },
140
+ {
141
+ name: "submittedAt",
142
+ header: "Submitted",
143
+ hideOnMobile: true,
144
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-sm text-muted-foreground", children: formatDate(row.submittedAt) })
145
+ }
146
+ ];
147
+ const fieldColumns = displayFields.map(
148
+ (field) => ({
149
+ name: `field:${field.name}`,
150
+ header: field.label,
151
+ hideOnMobile: true,
152
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-sm", children: formatValue(row.data[field.name]) })
153
+ })
154
+ );
155
+ return [...base, ...fieldColumns];
156
+ }, [displayFields]);
157
+ const selection = useMemo(
158
+ () => ({
159
+ selectedIds: Array.from(selectedIds),
160
+ onToggle: (submission) => toggleSelect(submission.id),
161
+ onToggleAll: (rows, allChecked) => {
162
+ const ids = rows.map((r) => r.id);
163
+ setSelectedIds((prev) => {
164
+ const next = new Set(prev);
165
+ if (allChecked) ids.forEach((id) => next.delete(id));
166
+ else ids.forEach((id) => next.add(id));
167
+ return next;
168
+ });
169
+ }
170
+ }),
171
+ [selectedIds, toggleSelect]
149
172
  );
173
+ const rowActions = useCallback(() => {
174
+ const actions = [];
175
+ if (onStatusChange) {
176
+ actions.push(
177
+ {
178
+ id: "mark-read",
179
+ label: "Mark Read",
180
+ isVisible: (r) => r.status !== "read",
181
+ isDisabled: (r) => processingIds.has(r.id),
182
+ onSelect: (r) => void handleStatusChange(r.id, "read")
183
+ },
184
+ {
185
+ id: "archive",
186
+ label: "Archive",
187
+ isVisible: (r) => r.status !== "archived",
188
+ isDisabled: (r) => processingIds.has(r.id),
189
+ onSelect: (r) => void handleStatusChange(r.id, "archived")
190
+ }
191
+ );
192
+ }
193
+ if (onDelete) {
194
+ actions.push({
195
+ id: "delete",
196
+ label: "Delete",
197
+ destructive: true,
198
+ isDisabled: (r) => processingIds.has(r.id),
199
+ onSelect: (r) => void handleDelete(r.id)
200
+ });
201
+ }
202
+ return actions;
203
+ }, [
204
+ onStatusChange,
205
+ onDelete,
206
+ processingIds,
207
+ handleStatusChange,
208
+ handleDelete
209
+ ]);
150
210
  return /* @__PURE__ */ jsxs("div", { className: "submission-list", children: [
151
211
  /* @__PURE__ */ jsxs("div", { className: "submission-list__toolbar", children: [
152
212
  /* @__PURE__ */ jsx("div", { className: "submission-list__filters", children: /* @__PURE__ */ jsxs(
@@ -238,119 +298,20 @@ function SubmissionList({
238
298
  ] })
239
299
  ] })
240
300
  ] }),
241
- /* @__PURE__ */ jsx("div", { className: "submission-list__table-container", children: /* @__PURE__ */ jsxs("table", { className: "submission-list__table", children: [
242
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
243
- /* @__PURE__ */ jsx("th", { className: "submission-list__th submission-list__th--checkbox", children: /* @__PURE__ */ jsx(
244
- "input",
245
- {
246
- type: "checkbox",
247
- checked: allSelected,
248
- onChange: toggleSelectAll,
249
- "aria-label": "Select all submissions"
250
- }
251
- ) }),
252
- /* @__PURE__ */ jsx("th", { className: "submission-list__th", children: "ID" }),
253
- /* @__PURE__ */ jsx("th", { className: "submission-list__th", children: "Status" }),
254
- /* @__PURE__ */ jsx("th", { className: "submission-list__th", children: "Submitted" }),
255
- displayFields.map((field) => /* @__PURE__ */ jsx("th", { className: "submission-list__th", children: field.label }, field.name)),
256
- /* @__PURE__ */ jsx("th", { className: "submission-list__th submission-list__th--actions", children: "Actions" })
257
- ] }) }),
258
- /* @__PURE__ */ jsx("tbody", { children: isLoading ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
259
- "td",
260
- {
261
- colSpan: 5 + displayFields.length,
262
- className: "submission-list__loading",
263
- children: "Loading submissions..."
264
- }
265
- ) }) : filteredSubmissions.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
266
- "td",
267
- {
268
- colSpan: 5 + displayFields.length,
269
- className: "submission-list__empty",
270
- children: "No submissions found"
271
- }
272
- ) }) : filteredSubmissions.map((submission) => {
273
- const isSelected = selectedIds.has(submission.id);
274
- const isProcessing = processingIds.has(submission.id);
275
- return /* @__PURE__ */ jsxs(
276
- "tr",
277
- {
278
- className: `submission-list__row ${isSelected ? "submission-list__row--selected" : ""} ${isProcessing ? "submission-list__row--processing" : ""}`,
279
- onClick: () => handleRowClick(submission),
280
- style: { cursor: onViewDetail ? "pointer" : "default" },
281
- children: [
282
- /* @__PURE__ */ jsx(
283
- "td",
284
- {
285
- className: "submission-list__td submission-list__td--checkbox",
286
- onClick: (e) => e.stopPropagation(),
287
- children: /* @__PURE__ */ jsx(
288
- "input",
289
- {
290
- type: "checkbox",
291
- checked: isSelected,
292
- onChange: () => toggleSelect(submission.id),
293
- disabled: isProcessing,
294
- "aria-label": `Select submission ${submission.id}`
295
- }
296
- )
297
- }
298
- ),
299
- /* @__PURE__ */ jsx("td", { className: "submission-list__td submission-list__td--id", children: /* @__PURE__ */ jsxs("code", { children: [
300
- submission.id.slice(0, 8),
301
- "..."
302
- ] }) }),
303
- /* @__PURE__ */ jsx("td", { className: "submission-list__td", children: /* @__PURE__ */ jsx("span", { className: getStatusBadgeClass(submission.status), children: submission.status }) }),
304
- /* @__PURE__ */ jsx("td", { className: "submission-list__td", children: formatDate(submission.submittedAt) }),
305
- displayFields.map((field) => /* @__PURE__ */ jsx("td", { className: "submission-list__td", children: formatValue(submission.data[field.name]) }, field.name)),
306
- /* @__PURE__ */ jsxs(
307
- "td",
308
- {
309
- className: "submission-list__td submission-list__td--actions",
310
- onClick: (e) => e.stopPropagation(),
311
- children: [
312
- onStatusChange && submission.status !== "read" && /* @__PURE__ */ jsx(
313
- "button",
314
- {
315
- type: "button",
316
- onClick: () => void handleStatusChange(submission.id, "read"),
317
- disabled: isProcessing,
318
- className: "submission-list__row-action",
319
- title: "Mark as read",
320
- children: "Mark Read"
321
- }
322
- ),
323
- onStatusChange && submission.status !== "archived" && /* @__PURE__ */ jsx(
324
- "button",
325
- {
326
- type: "button",
327
- onClick: () => void handleStatusChange(submission.id, "archived"),
328
- disabled: isProcessing,
329
- className: "submission-list__row-action",
330
- title: "Archive",
331
- children: "Archive"
332
- }
333
- ),
334
- onDelete && /* @__PURE__ */ jsx(
335
- "button",
336
- {
337
- type: "button",
338
- onClick: () => void handleDelete(submission.id),
339
- disabled: isProcessing,
340
- className: "submission-list__row-action submission-list__row-action--danger",
341
- title: "Delete",
342
- children: "Delete"
343
- }
344
- )
345
- ]
346
- }
347
- )
348
- ]
349
- },
350
- submission.id
351
- );
352
- }) })
353
- ] }) }),
301
+ /* @__PURE__ */ jsx(
302
+ DataTableView,
303
+ {
304
+ columns,
305
+ rows: filteredSubmissions,
306
+ loading: isLoading,
307
+ selection,
308
+ rowActions,
309
+ onRowClick: onViewDetail ? (submission) => onViewDetail(submission) : void 0,
310
+ registryKey: "form-submissions",
311
+ ariaLabel: "Form submissions table",
312
+ emptyMessage: "No submissions found"
313
+ }
314
+ ),
354
315
  /* @__PURE__ */ jsxs("div", { className: "submission-list__summary", children: [
355
316
  "Showing ",
356
317
  filteredSubmissions.length,
@@ -583,4 +544,4 @@ export {
583
544
  SubmissionList,
584
545
  SubmissionDetail
585
546
  };
586
- //# sourceMappingURL=chunk-ABNZFTQK.js.map
547
+ //# sourceMappingURL=chunk-2LLUO3TV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/submissions/SubmissionList.tsx","../src/components/submissions/SubmissionDetail.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Submission List Component\n *\n * Admin view for listing and managing form submissions.\n * Displays submissions in a filterable table with bulk actions.\n *\n * @module components/submissions/SubmissionList\n * @since 0.1.0\n */\n\n\"use client\";\n\nimport { DataTableView } from \"@nextlyhq/plugin-sdk/admin\";\nimport type {\n DataTableSelection,\n NextlyColumn,\n RowAction,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport type React from \"react\";\nimport { useState, useCallback, useMemo } from \"react\";\n\nimport type { SubmissionDocument, FormDocument, FormField } from \"../../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Props for the SubmissionList component.\n */\nexport interface SubmissionListProps {\n /** Form document containing field definitions */\n form: FormDocument;\n\n /** Array of submission documents to display */\n submissions: SubmissionDocument[];\n\n /** Whether data is currently loading */\n isLoading?: boolean;\n\n /** Callback when submission status is changed */\n onStatusChange?: (\n id: string,\n status: \"new\" | \"read\" | \"archived\"\n ) => Promise<void>;\n\n /** Callback when submission is deleted */\n onDelete?: (id: string) => Promise<void>;\n\n /** Callback when bulk delete is triggered */\n onBulkDelete?: (ids: string[]) => Promise<void>;\n\n /** Callback when bulk status change is triggered */\n onBulkStatusChange?: (\n ids: string[],\n status: \"new\" | \"read\" | \"archived\"\n ) => Promise<void>;\n\n /** Callback when export is triggered */\n onExport?: (format: \"csv\" | \"json\") => void;\n\n /** Callback when a submission is clicked for detail view */\n onViewDetail?: (submission: SubmissionDocument) => void;\n}\n\n/**\n * Status filter options.\n */\ntype StatusFilter = \"all\" | \"new\" | \"read\" | \"archived\";\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Format a value for display in the table.\n */\nfunction formatValue(value: unknown): string {\n if (value === null || value === undefined) return \"-\";\n if (Array.isArray(value)) return value.join(\", \");\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (value instanceof Date) return value.toLocaleDateString();\n let str: string;\n if (typeof value === \"object\") {\n str = JSON.stringify(value);\n } else {\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- value narrowed to primitive above; rule doesn't follow control flow on unknown\n str = String(value);\n }\n return str.length > 50 ? str.slice(0, 50) + \"...\" : str;\n}\n\n/**\n * Format a date for display.\n */\nfunction formatDate(date: Date | string): string {\n const d = typeof date === \"string\" ? new Date(date) : date;\n return d.toLocaleString();\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Submission List Component\n *\n * Displays form submissions in a table with filtering, selection, and actions.\n *\n * @example\n * ```tsx\n * <SubmissionList\n * form={form}\n * submissions={submissions}\n * onStatusChange={handleStatusChange}\n * onDelete={handleDelete}\n * onExport={handleExport}\n * onViewDetail={handleViewDetail}\n * />\n * ```\n */\nexport function SubmissionList({\n form,\n submissions,\n isLoading = false,\n onStatusChange,\n onDelete,\n onBulkDelete,\n onBulkStatusChange,\n onExport,\n onViewDetail,\n}: SubmissionListProps): React.ReactElement {\n // -------------------------------------------------------------------------\n // State\n // -------------------------------------------------------------------------\n\n const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());\n const [filter, setFilter] = useState<StatusFilter>(\"all\");\n const [processingIds, setProcessingIds] = useState<Set<string>>(new Set());\n\n // -------------------------------------------------------------------------\n // Computed Values\n // -------------------------------------------------------------------------\n\n /** Get display columns from form fields (first 3 fields) */\n const displayFields = useMemo((): FormField[] => {\n return form.fields.filter(f => f.type !== \"hidden\").slice(0, 3);\n }, [form.fields]);\n\n /** Filter submissions by status */\n const filteredSubmissions = useMemo((): SubmissionDocument[] => {\n if (filter === \"all\") return submissions;\n return submissions.filter(sub => sub.status === filter);\n }, [submissions, filter]);\n\n /** Check if some submissions are selected */\n const someSelected = selectedIds.size > 0;\n\n // -------------------------------------------------------------------------\n // Handlers\n // -------------------------------------------------------------------------\n\n /** Toggle selection for a single submission */\n const toggleSelect = useCallback((id: string) => {\n setSelectedIds(prev => {\n const next = new Set(prev);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n return next;\n });\n }, []);\n\n /** Handle status change for a single submission */\n const handleStatusChange = useCallback(\n async (id: string, status: \"new\" | \"read\" | \"archived\") => {\n if (!onStatusChange) return;\n\n setProcessingIds(prev => new Set(prev).add(id));\n try {\n await onStatusChange(id, status);\n } finally {\n setProcessingIds(prev => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n }\n },\n [onStatusChange]\n );\n\n /** Handle delete for a single submission */\n const handleDelete = useCallback(\n async (id: string) => {\n if (!onDelete) return;\n if (!window.confirm(\"Are you sure you want to delete this submission?\")) {\n return;\n }\n\n setProcessingIds(prev => new Set(prev).add(id));\n try {\n await onDelete(id);\n setSelectedIds(prev => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n } finally {\n setProcessingIds(prev => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n }\n },\n [onDelete]\n );\n\n /** Handle bulk delete */\n const handleBulkDelete = useCallback(async () => {\n if (!onBulkDelete || selectedIds.size === 0) return;\n if (\n !window.confirm(\n `Are you sure you want to delete ${selectedIds.size} submission(s)?`\n )\n ) {\n return;\n }\n\n const ids = Array.from(selectedIds);\n ids.forEach(id => setProcessingIds(prev => new Set(prev).add(id)));\n\n try {\n await onBulkDelete(ids);\n setSelectedIds(new Set());\n } finally {\n setProcessingIds(new Set());\n }\n }, [onBulkDelete, selectedIds]);\n\n /** Handle bulk status change */\n const handleBulkStatusChange = useCallback(\n async (status: \"new\" | \"read\" | \"archived\") => {\n if (!onBulkStatusChange || selectedIds.size === 0) return;\n\n const ids = Array.from(selectedIds);\n ids.forEach(id => setProcessingIds(prev => new Set(prev).add(id)));\n\n try {\n await onBulkStatusChange(ids, status);\n setSelectedIds(new Set());\n } finally {\n setProcessingIds(new Set());\n }\n },\n [onBulkStatusChange, selectedIds]\n );\n\n // -------------------------------------------------------------------------\n // DataTable wiring (columns, selection, row actions)\n // -------------------------------------------------------------------------\n\n const columns = useMemo<NextlyColumn<SubmissionDocument>[]>(() => {\n const base: NextlyColumn<SubmissionDocument>[] = [\n {\n name: \"id\",\n header: \"ID\",\n cell: ({ row }) => (\n <code className=\"font-mono text-xs text-muted-foreground\">\n {row.id.slice(0, 8)}...\n </code>\n ),\n },\n {\n name: \"status\",\n header: \"Status\",\n cell: ({ row }) => (\n <span className=\"inline-flex items-center rounded-none border border-border bg-muted px-2 py-0.5 text-[11px] font-medium capitalize text-foreground\">\n {row.status}\n </span>\n ),\n },\n {\n name: \"submittedAt\",\n header: \"Submitted\",\n hideOnMobile: true,\n cell: ({ row }) => (\n <span className=\"whitespace-nowrap text-sm text-muted-foreground\">\n {formatDate(row.submittedAt)}\n </span>\n ),\n },\n ];\n const fieldColumns: NextlyColumn<SubmissionDocument>[] = displayFields.map(\n field => ({\n name: `field:${field.name}`,\n header: field.label,\n hideOnMobile: true,\n cell: ({ row }) => (\n <span className=\"text-sm\">{formatValue(row.data[field.name])}</span>\n ),\n })\n );\n return [...base, ...fieldColumns];\n }, [displayFields]);\n\n const selection = useMemo<DataTableSelection<SubmissionDocument>>(\n () => ({\n selectedIds: Array.from(selectedIds),\n onToggle: submission => toggleSelect(submission.id),\n onToggleAll: (rows, allChecked) => {\n const ids = rows.map(r => r.id);\n setSelectedIds(prev => {\n const next = new Set(prev);\n if (allChecked) ids.forEach(id => next.delete(id));\n else ids.forEach(id => next.add(id));\n return next;\n });\n },\n }),\n [selectedIds, toggleSelect]\n );\n\n const rowActions = useCallback((): RowAction<SubmissionDocument>[] => {\n const actions: RowAction<SubmissionDocument>[] = [];\n if (onStatusChange) {\n actions.push(\n {\n id: \"mark-read\",\n label: \"Mark Read\",\n isVisible: r => r.status !== \"read\",\n isDisabled: r => processingIds.has(r.id),\n onSelect: r => void handleStatusChange(r.id, \"read\"),\n },\n {\n id: \"archive\",\n label: \"Archive\",\n isVisible: r => r.status !== \"archived\",\n isDisabled: r => processingIds.has(r.id),\n onSelect: r => void handleStatusChange(r.id, \"archived\"),\n }\n );\n }\n if (onDelete) {\n actions.push({\n id: \"delete\",\n label: \"Delete\",\n destructive: true,\n isDisabled: r => processingIds.has(r.id),\n onSelect: r => void handleDelete(r.id),\n });\n }\n return actions;\n }, [\n onStatusChange,\n onDelete,\n processingIds,\n handleStatusChange,\n handleDelete,\n ]);\n\n // -------------------------------------------------------------------------\n // Render\n // -------------------------------------------------------------------------\n\n return (\n <div className=\"submission-list\">\n {/* Toolbar */}\n <div className=\"submission-list__toolbar\">\n <div className=\"submission-list__filters\">\n <select\n value={filter}\n onChange={e => setFilter(e.target.value as StatusFilter)}\n className=\"submission-list__filter-select\"\n aria-label=\"Filter by status\"\n >\n <option value=\"all\">All Submissions ({submissions.length})</option>\n <option value=\"new\">\n New ({submissions.filter(s => s.status === \"new\").length})\n </option>\n <option value=\"read\">\n Read ({submissions.filter(s => s.status === \"read\").length})\n </option>\n <option value=\"archived\">\n Archived (\n {submissions.filter(s => s.status === \"archived\").length})\n </option>\n </select>\n </div>\n\n <div className=\"submission-list__actions\">\n {/* Bulk Actions */}\n {someSelected && (\n <div className=\"submission-list__bulk-actions\">\n <span className=\"submission-list__selected-count\">\n {selectedIds.size} selected\n </span>\n {onBulkStatusChange && (\n <>\n <button\n type=\"button\"\n onClick={() => void handleBulkStatusChange(\"read\")}\n className=\"submission-list__action-btn\"\n >\n Mark Read\n </button>\n <button\n type=\"button\"\n onClick={() => void handleBulkStatusChange(\"archived\")}\n className=\"submission-list__action-btn\"\n >\n Archive\n </button>\n </>\n )}\n {onBulkDelete && (\n <button\n type=\"button\"\n onClick={() => void handleBulkDelete()}\n className=\"submission-list__action-btn submission-list__action-btn--danger\"\n >\n Delete\n </button>\n )}\n </div>\n )}\n\n {/* Export Actions */}\n {onExport && (\n <div className=\"submission-list__export-actions\">\n <button\n type=\"button\"\n onClick={() => onExport(\"csv\")}\n className=\"submission-list__export-btn\"\n >\n Export CSV\n </button>\n <button\n type=\"button\"\n onClick={() => onExport(\"json\")}\n className=\"submission-list__export-btn\"\n >\n Export JSON\n </button>\n </div>\n )}\n </div>\n </div>\n\n {/* Table (unified admin DataTable via the plugin SDK) */}\n <DataTableView<SubmissionDocument>\n columns={columns}\n rows={filteredSubmissions}\n loading={isLoading}\n selection={selection}\n rowActions={rowActions}\n onRowClick={\n onViewDetail ? submission => onViewDetail(submission) : undefined\n }\n registryKey=\"form-submissions\"\n ariaLabel=\"Form submissions table\"\n emptyMessage=\"No submissions found\"\n />\n\n {/* Summary */}\n <div className=\"submission-list__summary\">\n Showing {filteredSubmissions.length} of {submissions.length} submissions\n </div>\n </div>\n );\n}\n","\"use client\";\n\n/**\n * Submission Detail Component\n *\n * Admin view for viewing and managing a single form submission.\n * Displays all submission data, metadata, and provides status management.\n *\n * @module components/submissions/SubmissionDetail\n * @since 0.1.0\n */\n\n\"use client\";\n\nimport type React from \"react\";\nimport { useState, useCallback } from \"react\";\n\nimport type { SubmissionDocument, FormDocument, FormField } from \"../../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Props for the SubmissionDetail component.\n */\nexport interface SubmissionDetailProps {\n /** Form document containing field definitions */\n form: FormDocument;\n\n /** Submission document to display */\n submission: SubmissionDocument;\n\n /** Callback when status is changed */\n onStatusChange?: (status: \"new\" | \"read\" | \"archived\") => Promise<void>;\n\n /** Callback when notes are updated */\n onNotesUpdate?: (notes: string) => Promise<void>;\n\n /** Callback when back button is clicked */\n onBack?: () => void;\n\n /** Callback when delete is triggered */\n onDelete?: () => Promise<void>;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Stringify a value safely, falling back to JSON for plain objects so we\n * never render the default `[object Object]` representation.\n */\nfunction safeString(value: unknown): string {\n if (typeof value === \"object\" && value !== null) {\n return JSON.stringify(value);\n }\n return String(value);\n}\n\n/**\n * Format a value for display based on field type.\n */\nfunction formatFieldValue(value: unknown, field: FormField): string {\n if (value === null || value === undefined) return \"-\";\n\n switch (field.type) {\n case \"checkbox\":\n return value ? \"Yes\" : \"No\";\n\n case \"select\":\n if (Array.isArray(value)) {\n return value.join(\", \");\n }\n return safeString(value);\n\n case \"file\":\n if (Array.isArray(value)) {\n return value.map(v => safeString(v)).join(\", \");\n }\n return safeString(value);\n\n case \"date\":\n if (value instanceof Date) {\n return value.toLocaleDateString();\n }\n if (typeof value === \"string\") {\n return new Date(value).toLocaleDateString();\n }\n return safeString(value);\n\n case \"time\":\n return safeString(value);\n\n default:\n return safeString(value);\n }\n}\n\n/**\n * Format a date for display.\n */\nfunction formatDate(date: Date | string): string {\n const d = typeof date === \"string\" ? new Date(date) : date;\n return d.toLocaleString();\n}\n\n/**\n * Get status options.\n */\nconst STATUS_OPTIONS: Array<{\n value: \"new\" | \"read\" | \"archived\";\n label: string;\n}> = [\n { value: \"new\", label: \"New\" },\n { value: \"read\", label: \"Read\" },\n { value: \"archived\", label: \"Archived\" },\n];\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Submission Detail Component\n *\n * Displays full submission data with status management and notes.\n *\n * @example\n * ```tsx\n * <SubmissionDetail\n * form={form}\n * submission={submission}\n * onStatusChange={handleStatusChange}\n * onNotesUpdate={handleNotesUpdate}\n * onBack={handleBack}\n * />\n * ```\n */\nexport function SubmissionDetail({\n form,\n submission,\n onStatusChange,\n onNotesUpdate,\n onBack,\n onDelete,\n}: SubmissionDetailProps): React.ReactElement {\n // -------------------------------------------------------------------------\n // State\n // -------------------------------------------------------------------------\n\n const [isProcessing, setIsProcessing] = useState(false);\n const [notes, setNotes] = useState(\n (submission as unknown as { notes?: string }).notes || \"\"\n );\n const [notesModified, setNotesModified] = useState(false);\n\n // -------------------------------------------------------------------------\n // Handlers\n // -------------------------------------------------------------------------\n\n /** Handle status change */\n const handleStatusChange = useCallback(\n async (event: React.ChangeEvent<HTMLSelectElement>) => {\n if (!onStatusChange) return;\n\n const newStatus = event.target.value as \"new\" | \"read\" | \"archived\";\n setIsProcessing(true);\n\n try {\n await onStatusChange(newStatus);\n } finally {\n setIsProcessing(false);\n }\n },\n [onStatusChange]\n );\n\n /** Handle notes change */\n const handleNotesChange = useCallback(\n (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setNotes(event.target.value);\n setNotesModified(true);\n },\n []\n );\n\n /** Handle notes save */\n const handleNotesSave = useCallback(async () => {\n if (!onNotesUpdate || !notesModified) return;\n\n setIsProcessing(true);\n try {\n await onNotesUpdate(notes);\n setNotesModified(false);\n } finally {\n setIsProcessing(false);\n }\n }, [onNotesUpdate, notes, notesModified]);\n\n /** Handle delete */\n const handleDelete = useCallback(async () => {\n if (!onDelete) return;\n if (!window.confirm(\"Are you sure you want to delete this submission?\")) {\n return;\n }\n\n setIsProcessing(true);\n try {\n await onDelete();\n } finally {\n setIsProcessing(false);\n }\n }, [onDelete]);\n\n // -------------------------------------------------------------------------\n // Computed Values\n // -------------------------------------------------------------------------\n\n /** Get displayable fields */\n const displayFields = form.fields;\n\n /** Get submission data */\n const data = submission.data;\n\n // -------------------------------------------------------------------------\n // Render\n // -------------------------------------------------------------------------\n\n return (\n <div className=\"submission-detail\">\n {/* Header */}\n <div className=\"submission-detail__header\">\n <div className=\"submission-detail__header-left\">\n {onBack && (\n <button\n type=\"button\"\n onClick={onBack}\n className=\"submission-detail__back-btn\"\n disabled={isProcessing}\n >\n &larr; Back to List\n </button>\n )}\n <h2 className=\"submission-detail__title\">Submission Details</h2>\n </div>\n\n <div className=\"submission-detail__header-right\">\n {onStatusChange && (\n <div className=\"submission-detail__status-control\">\n <label htmlFor=\"submission-status\">Status:</label>\n <select\n id=\"submission-status\"\n value={submission.status}\n onChange={e => void handleStatusChange(e)}\n disabled={isProcessing}\n className=\"submission-detail__status-select\"\n >\n {STATUS_OPTIONS.map(option => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {onDelete && (\n <button\n type=\"button\"\n onClick={() => void handleDelete()}\n disabled={isProcessing}\n className=\"submission-detail__delete-btn\"\n >\n Delete\n </button>\n )}\n </div>\n </div>\n\n {/* Submission ID */}\n <div className=\"submission-detail__id\">\n <span className=\"submission-detail__id-label\">Submission ID:</span>\n <code className=\"submission-detail__id-value\">{submission.id}</code>\n </div>\n\n {/* Form Fields Data */}\n <div className=\"submission-detail__section\">\n <h3 className=\"submission-detail__section-title\">Submitted Data</h3>\n <div className=\"submission-detail__fields\">\n {displayFields.map(field => (\n <div key={field.name} className=\"submission-detail__field\">\n <div className=\"submission-detail__field-label\">\n {field.label}\n </div>\n <div className=\"submission-detail__field-value\">\n {formatFieldValue(data[field.name], field)}\n </div>\n </div>\n ))}\n\n {displayFields.length === 0 && (\n <div className=\"submission-detail__empty\">No fields to display</div>\n )}\n </div>\n </div>\n\n {/* Notes Section */}\n {onNotesUpdate && (\n <div className=\"submission-detail__section\">\n <h3 className=\"submission-detail__section-title\">Internal Notes</h3>\n <div className=\"submission-detail__notes\">\n <textarea\n value={notes}\n onChange={handleNotesChange}\n placeholder=\"Add internal notes about this submission...\"\n disabled={isProcessing}\n className=\"submission-detail__notes-textarea\"\n rows={4}\n />\n {notesModified && (\n <button\n type=\"button\"\n onClick={() => void handleNotesSave()}\n disabled={isProcessing}\n className=\"submission-detail__notes-save-btn\"\n >\n Save Notes\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Metadata Section */}\n <div className=\"submission-detail__section\">\n <h3 className=\"submission-detail__section-title\">Metadata</h3>\n <div className=\"submission-detail__metadata\">\n <div className=\"submission-detail__metadata-item\">\n <span className=\"submission-detail__metadata-label\">\n Submitted At:\n </span>\n <span className=\"submission-detail__metadata-value\">\n {formatDate(submission.submittedAt)}\n </span>\n </div>\n\n {submission.ipAddress && (\n <div className=\"submission-detail__metadata-item\">\n <span className=\"submission-detail__metadata-label\">\n IP Address:\n </span>\n <span className=\"submission-detail__metadata-value\">\n <code>{submission.ipAddress}</code>\n </span>\n </div>\n )}\n\n {submission.userAgent && (\n <div className=\"submission-detail__metadata-item\">\n <span className=\"submission-detail__metadata-label\">\n User Agent:\n </span>\n <span className=\"submission-detail__metadata-value submission-detail__metadata-value--truncate\">\n {submission.userAgent}\n </span>\n </div>\n )}\n\n <div className=\"submission-detail__metadata-item\">\n <span className=\"submission-detail__metadata-label\">\n Created At:\n </span>\n <span className=\"submission-detail__metadata-value\">\n {formatDate(submission.createdAt)}\n </span>\n </div>\n\n <div className=\"submission-detail__metadata-item\">\n <span className=\"submission-detail__metadata-label\">\n Updated At:\n </span>\n <span className=\"submission-detail__metadata-value\">\n {formatDate(submission.updatedAt)}\n </span>\n </div>\n </div>\n </div>\n\n {/* Raw Data Section (Expandable) */}\n <details className=\"submission-detail__raw-data\">\n <summary className=\"submission-detail__raw-data-summary\">\n View Raw JSON Data\n </summary>\n <pre className=\"submission-detail__raw-data-content\">\n {JSON.stringify(submission.data, null, 2)}\n </pre>\n </details>\n </div>\n );\n}\n"],"mappings":"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","names":["useState","useCallback","jsx","jsxs","formatDate"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SubmissionDetail,
3
3
  SubmissionList
4
- } from "../chunk-ABNZFTQK.js";
4
+ } from "../chunk-2LLUO3TV.js";
5
5
  export {
6
6
  SubmissionDetail,
7
7
  SubmissionList
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/plugin.ts
2
+ import { createRequire } from "module";
2
3
  import { definePlugin } from "@nextlyhq/plugin-sdk";
3
4
 
4
5
  // src/collections/forms.ts
@@ -424,6 +425,9 @@ function submissionsCollection(pluginConfig) {
424
425
  }
425
426
 
426
427
  // src/plugin.ts
428
+ var { version: PLUGIN_VERSION } = createRequire(import.meta.url)(
429
+ "../package.json"
430
+ );
427
431
  function resolveConfig(options) {
428
432
  const formOverrides = options.formOverrides || {};
429
433
  const submissionOverrides = options.formSubmissionOverrides || {};
@@ -497,8 +501,7 @@ function formBuilder(options = {}) {
497
501
  const submissionsCol = submissionsCollection(resolvedConfig);
498
502
  const plugin = definePlugin({
499
503
  name: "@nextlyhq/plugin-form-builder",
500
- // Keep in sync with package.json `version` (guarded by package-metadata.test).
501
- version: "0.0.2-alpha.24",
504
+ version: PLUGIN_VERSION,
502
505
  nextly: ">=0.0.2-alpha.21",
503
506
  // Declarative schema: the merged pipeline folds these into the
504
507
  // app schema — no manual `setup()` append needed. Just register the plugin.