anton-bakker-feedback 0.3.1 → 0.3.3
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/dist/react/admin/FeedbackDetailPage.d.ts +2 -1
- package/dist/react/admin/FeedbackDetailPage.d.ts.map +1 -1
- package/dist/react/admin/index.js +50 -4
- package/dist/react/admin/index.js.map +1 -1
- package/dist/react/admin/types.d.ts +2 -0
- package/dist/react/admin/types.d.ts.map +1 -1
- package/package.json +6 -2
|
@@ -4,7 +4,8 @@ import { FeedbackDetailPageProps } from './types.js';
|
|
|
4
4
|
* Location: src/react/admin/FeedbackDetailPage.tsx
|
|
5
5
|
*
|
|
6
6
|
* Admin detail page for viewing a single feedback item.
|
|
7
|
-
*
|
|
7
|
+
* Renders structured content (bug reports, feature requests) with all fields,
|
|
8
|
+
* screenshot, console errors, and dialogue history.
|
|
8
9
|
*/
|
|
9
10
|
import * as React from 'react';
|
|
10
11
|
declare const FeedbackDetailPage: React.ForwardRefExoticComponent<FeedbackDetailPageProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FeedbackDetailPage.d.ts","sourceRoot":"","sources":["../../../src/react/admin/FeedbackDetailPage.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"FeedbackDetailPage.d.ts","sourceRoot":"","sources":["../../../src/react/admin/FeedbackDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAkC1D,QAAA,MAAM,kBAAkB,gGAuKvB,CAAC;AAGF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -115,11 +115,31 @@ const FeedbackListPage = React.forwardRef(
|
|
|
115
115
|
}
|
|
116
116
|
);
|
|
117
117
|
FeedbackListPage.displayName = "FeedbackListPage";
|
|
118
|
+
function parseStructuredContent(content) {
|
|
119
|
+
const lines = content.split("\n");
|
|
120
|
+
let header;
|
|
121
|
+
const fields = [];
|
|
122
|
+
for (const line of lines) {
|
|
123
|
+
const trimmed = line.trim();
|
|
124
|
+
if (!trimmed) continue;
|
|
125
|
+
const headerMatch = trimmed.match(/^\[(.+)\]$/);
|
|
126
|
+
if (headerMatch && headerMatch[1]) {
|
|
127
|
+
header = headerMatch[1];
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const fieldMatch = trimmed.match(/^([A-Z][A-Za-z\s]+):\s*(.+)$/);
|
|
131
|
+
if (fieldMatch && fieldMatch[1] && fieldMatch[2]) {
|
|
132
|
+
fields.push({ label: fieldMatch[1], value: fieldMatch[2] });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { header, fields, raw: content };
|
|
136
|
+
}
|
|
118
137
|
const FeedbackDetailPage = React.forwardRef(
|
|
119
|
-
({ feedbackId, fetchItem, onNavigate, currentUser, className }, ref) => {
|
|
138
|
+
({ feedbackId, fetchItem, getScreenshotUrl, onNavigate, currentUser, className }, ref) => {
|
|
120
139
|
const [item, setItem] = React.useState(null);
|
|
121
140
|
const [loading, setLoading] = React.useState(true);
|
|
122
141
|
const [error, setError] = React.useState(null);
|
|
142
|
+
const [screenshotUrl, setScreenshotUrl] = React.useState(null);
|
|
123
143
|
React.useEffect(() => {
|
|
124
144
|
let cancelled = false;
|
|
125
145
|
setLoading(true);
|
|
@@ -135,6 +155,11 @@ const FeedbackDetailPage = React.forwardRef(
|
|
|
135
155
|
cancelled = true;
|
|
136
156
|
};
|
|
137
157
|
}, [feedbackId, fetchItem]);
|
|
158
|
+
React.useEffect(() => {
|
|
159
|
+
if (item?.hasScreenshot && item.screenshotS3Key && getScreenshotUrl) {
|
|
160
|
+
getScreenshotUrl(item.screenshotS3Key).then(setScreenshotUrl).catch(() => setScreenshotUrl(null));
|
|
161
|
+
}
|
|
162
|
+
}, [item?.hasScreenshot, item?.screenshotS3Key, getScreenshotUrl]);
|
|
138
163
|
if (error) {
|
|
139
164
|
return /* @__PURE__ */ jsx("div", { ref, className, children: /* @__PURE__ */ jsx(DetailPage, { title: "Feedback", headerActions: /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onNavigate("/feedback"), children: "Back to list" }), children: /* @__PURE__ */ jsx("div", { className: "p-6 text-destructive", children: error }) }) });
|
|
140
165
|
}
|
|
@@ -142,10 +167,12 @@ const FeedbackDetailPage = React.forwardRef(
|
|
|
142
167
|
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onNavigate("/feedback"), children: "Back to list" }),
|
|
143
168
|
/* @__PURE__ */ jsx(Button, { onClick: () => onNavigate(`/feedback/${feedbackId}/edit`), children: "Edit" })
|
|
144
169
|
] });
|
|
170
|
+
const parsed = item ? parseStructuredContent(item.content) : null;
|
|
171
|
+
const hasStructuredFields = parsed && parsed.fields.length > 0;
|
|
145
172
|
return /* @__PURE__ */ jsx("div", { ref, className, children: /* @__PURE__ */ jsx(
|
|
146
173
|
DetailPage,
|
|
147
174
|
{
|
|
148
|
-
title: item?.content?.
|
|
175
|
+
title: item?.content?.split("\n").find((l) => l.match(/^Summary:/))?.replace("Summary: ", "") || parsed?.header || `Feedback ${feedbackId.slice(0, 12)}…`,
|
|
149
176
|
headerActions,
|
|
150
177
|
loading,
|
|
151
178
|
testId: "feedback-detail-page",
|
|
@@ -161,15 +188,34 @@ const FeedbackDetailPage = React.forwardRef(
|
|
|
161
188
|
item.scope && /* @__PURE__ */ jsx(DetailRow, { label: "Scope", children: /* @__PURE__ */ jsx(Badge, { variant: "outline", children: item.scope }) }),
|
|
162
189
|
item.size && /* @__PURE__ */ jsx(DetailRow, { label: "Size", children: /* @__PURE__ */ jsx(Badge, { variant: "outline", children: item.size }) })
|
|
163
190
|
] }),
|
|
164
|
-
/* @__PURE__ */ jsx(DetailSection, { title: "Content", children:
|
|
191
|
+
/* @__PURE__ */ jsx(DetailSection, { title: "Content", children: hasStructuredFields ? (
|
|
192
|
+
// Render each structured field as a separate row
|
|
193
|
+
parsed.fields.map((field, i) => /* @__PURE__ */ jsx(DetailRow, { label: field.label, children: /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: field.value }) }, i))
|
|
194
|
+
) : (
|
|
195
|
+
// Fallback: render raw content
|
|
196
|
+
/* @__PURE__ */ jsx(DetailRow, { label: "Description", children: /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: item.content }) })
|
|
197
|
+
) }),
|
|
198
|
+
item.hasScreenshot && /* @__PURE__ */ jsx(DetailSection, { title: "Screenshot", children: /* @__PURE__ */ jsx(DetailRow, { label: "Capture", children: screenshotUrl ? /* @__PURE__ */ jsx("a", { href: screenshotUrl, target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsx(
|
|
199
|
+
"img",
|
|
200
|
+
{
|
|
201
|
+
src: screenshotUrl,
|
|
202
|
+
alt: "Feedback screenshot",
|
|
203
|
+
className: "max-w-full max-h-[400px] rounded border cursor-pointer hover:opacity-90 transition-opacity"
|
|
204
|
+
}
|
|
205
|
+
) }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: getScreenshotUrl ? "Loading screenshot…" : "Screenshot available (no viewer configured)" }) }) }),
|
|
206
|
+
item.context?.consoleErrors && item.context.consoleErrors.length > 0 && /* @__PURE__ */ jsx(DetailSection, { title: "Console Errors", collapsible: true, defaultOpen: true, children: /* @__PURE__ */ jsx(DetailRow, { label: `${item.context.consoleErrors.length} error(s)`, children: /* @__PURE__ */ jsx("ul", { className: "list-none space-y-1", children: item.context.consoleErrors.map((err, i) => /* @__PURE__ */ jsx("li", { className: "font-mono text-xs text-destructive bg-destructive/5 p-2 rounded", children: err }, i)) }) }) }),
|
|
165
207
|
item.dialogueHistory && item.dialogueHistory.length > 0 && /* @__PURE__ */ jsx(DetailSection, { title: "Dialogue History", collapsible: true, defaultOpen: false, children: item.dialogueHistory.map((entry, i) => /* @__PURE__ */ jsxs(DetailRow, { label: entry.role === "user" ? "User" : "Assistant", children: [
|
|
166
208
|
/* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap text-sm", children: entry.content }),
|
|
167
209
|
entry.timestamp && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: new Date(entry.timestamp).toLocaleString() })
|
|
168
210
|
] }, i)) }),
|
|
169
211
|
item.context && /* @__PURE__ */ jsxs(DetailSection, { title: "Browser Context", collapsible: true, defaultOpen: false, children: [
|
|
170
212
|
item.context.url && /* @__PURE__ */ jsx(DetailRow, { label: "URL", children: item.context.url }),
|
|
213
|
+
item.context.pageTitle && /* @__PURE__ */ jsx(DetailRow, { label: "Page", children: item.context.pageTitle }),
|
|
171
214
|
item.context.userAgent && /* @__PURE__ */ jsx(DetailRow, { label: "User Agent", children: item.context.userAgent }),
|
|
172
|
-
item.context.viewport && /* @__PURE__ */ jsx(DetailRow, { label: "Viewport", children: `${item.context.viewport.width}×${item.context.viewport.height}` })
|
|
215
|
+
item.context.viewport && /* @__PURE__ */ jsx(DetailRow, { label: "Viewport", children: `${item.context.viewport.width}×${item.context.viewport.height}` }),
|
|
216
|
+
item.context.locale && /* @__PURE__ */ jsx(DetailRow, { label: "Locale", children: item.context.locale }),
|
|
217
|
+
item.context.timezone && /* @__PURE__ */ jsx(DetailRow, { label: "Timezone", children: item.context.timezone }),
|
|
218
|
+
item.context.performanceMs != null && /* @__PURE__ */ jsx(DetailRow, { label: "Page Load", children: `${item.context.performanceMs}ms` })
|
|
173
219
|
] }),
|
|
174
220
|
item.tags && item.tags.length > 0 && /* @__PURE__ */ jsx(DetailSection, { title: "Tags", children: /* @__PURE__ */ jsx(DetailRow, { label: "Tags", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: item.tags.map((tag) => /* @__PURE__ */ jsx(Badge, { variant: "outline", children: tag }, tag)) }) }) })
|
|
175
221
|
] })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/react/admin/FeedbackListPage.tsx","../../../src/react/admin/FeedbackDetailPage.tsx","../../../src/react/admin/hooks/useStatusTransition.ts","../../../src/react/admin/FeedbackUpdatePage.tsx"],"sourcesContent":["/**\n * FeedbackListPage.tsx\n * Location: src/react/admin/FeedbackListPage.tsx\n *\n * Admin list page for browsing and filtering feedback items.\n * Thin wrapper around ListPage from anton-bakker-tailwind-components\n * for visual consistency with sagrosoft20.\n */\nimport * as React from 'react';\nimport { ListPage, Badge } from 'anton-bakker-tailwind-components';\nimport type { SimpleColumn, FilterBarProps } from 'anton-bakker-tailwind-components';\nimport type { StoredFeedbackItem, FeedbackStatus, FeedbackType } from '../../core/types.js';\nimport type { FeedbackListPageProps } from './types.js';\n\nconst STATUS_VARIANT: Record<FeedbackStatus, 'default' | 'success' | 'warning' | 'destructive' | 'secondary'> = {\n open: 'warning',\n approved: 'default',\n deferred: 'secondary',\n resolved: 'success',\n wontfix: 'destructive',\n};\n\nconst TYPE_VARIANT: Record<FeedbackType, 'default' | 'success' | 'warning' | 'destructive' | 'secondary'> = {\n bug: 'destructive',\n feature: 'default',\n ux: 'warning',\n other: 'secondary',\n};\n\nfunction StatusBadge({ status }: { status: FeedbackStatus }) {\n return <Badge variant={STATUS_VARIANT[status] ?? 'secondary'}>{status}</Badge>;\n}\n\nfunction TypeBadge({ type }: { type: FeedbackType }) {\n return <Badge variant={TYPE_VARIANT[type] ?? 'secondary'}>{type}</Badge>;\n}\n\nconst FeedbackListPage = React.forwardRef<HTMLDivElement, FeedbackListPageProps>(\n ({ items, loading = false, onNavigate, className, emptyStateMessage, currentUser, ...rest }, ref) => {\n const [search, setSearch] = React.useState('');\n const [statusFilter, setStatusFilter] = React.useState('');\n const [typeFilter, setTypeFilter] = React.useState('');\n\n const filteredItems = React.useMemo(() => {\n let result = items;\n if (statusFilter) result = result.filter((i) => i.status === statusFilter);\n if (typeFilter) result = result.filter((i) => i.type === typeFilter);\n if (search) {\n const q = search.toLowerCase();\n result = result.filter((i) => i.content?.toLowerCase().includes(q) || i.id.toLowerCase().includes(q));\n }\n return result;\n }, [items, statusFilter, typeFilter, search]);\n\n const columns: SimpleColumn<StoredFeedbackItem>[] = React.useMemo(() => [\n { header: 'Content', accessor: (row) => row.content?.slice(0, 60) || row.id, sortable: true },\n { header: 'Type', accessor: (row) => <TypeBadge type={row.type} />, sortable: true },\n { header: 'Status', accessor: (row) => <StatusBadge status={row.status} />, sortable: true },\n { header: 'Priority', accessor: 'priority', sortable: true },\n { header: 'Reporters', accessor: 'reporterCount', sortable: true },\n {\n header: 'Date',\n accessor: (row) => row.submittedAt ? new Date(row.submittedAt).toLocaleDateString() : '—',\n sortable: true,\n },\n ], []);\n\n const filterBar: FilterBarProps | undefined = (statusFilter || typeFilter) ? {\n filters: [\n ...(statusFilter ? [{ field: 'status', label: `Status: ${statusFilter}`, value: statusFilter }] : []),\n ...(typeFilter ? [{ field: 'type', label: `Type: ${typeFilter}`, value: typeFilter }] : []),\n ],\n onFilterRemove: (field: string) => {\n if (field === 'status') setStatusFilter('');\n if (field === 'type') setTypeFilter('');\n },\n onFiltersClear: () => { setStatusFilter(''); setTypeFilter(''); },\n } : undefined;\n\n const headerActions = (\n <div className=\"flex items-center gap-2\">\n <select\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value)}\n className=\"h-8 rounded-md border border-border bg-transparent px-2 text-sm\"\n aria-label=\"Filter by status\"\n >\n <option value=\"\">All statuses</option>\n <option value=\"open\">Open</option>\n <option value=\"approved\">Approved</option>\n <option value=\"deferred\">Deferred</option>\n <option value=\"resolved\">Resolved</option>\n <option value=\"wontfix\">Won't fix</option>\n </select>\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value)}\n className=\"h-8 rounded-md border border-border bg-transparent px-2 text-sm\"\n aria-label=\"Filter by type\"\n >\n <option value=\"\">All types</option>\n <option value=\"bug\">Bug</option>\n <option value=\"feature\">Feature</option>\n <option value=\"ux\">UX</option>\n <option value=\"other\">Other</option>\n </select>\n </div>\n );\n\n const listProps = {\n title: 'Feedback' as const,\n columns,\n items: filteredItems,\n loading,\n onRowClick: (row: StoredFeedbackItem) => onNavigate(`/feedback/${row.id}`),\n search,\n onSearchChange: setSearch,\n searchPlaceholder: 'Search feedback...',\n headerActions,\n testId: 'feedback-list-page',\n ...(filterBar ? { filterBar } : {}),\n ...(emptyStateMessage ? { emptyState: emptyStateMessage } : {}),\n ...(className ? { className } : {}),\n };\n\n return <ListPage<StoredFeedbackItem> ref={ref} {...listProps} />;\n },\n);\nFeedbackListPage.displayName = 'FeedbackListPage';\n\nexport { FeedbackListPage, StatusBadge, TypeBadge };\n","/**\n * FeedbackDetailPage.tsx\n * Location: src/react/admin/FeedbackDetailPage.tsx\n *\n * Admin detail page for viewing a single feedback item.\n * Uses DetailPage + DetailSection + DetailRow from anton-bakker-tailwind-components.\n */\nimport * as React from 'react';\nimport { DetailPage, DetailSection, DetailRow, Badge, Button } from 'anton-bakker-tailwind-components';\nimport type { StoredFeedbackItem } from '../../core/types.js';\nimport type { FeedbackDetailPageProps } from './types.js';\nimport { StatusBadge, TypeBadge } from './FeedbackListPage.js';\n\nconst FeedbackDetailPage = React.forwardRef<HTMLDivElement, FeedbackDetailPageProps>(\n ({ feedbackId, fetchItem, onNavigate, currentUser, className }, ref) => {\n const [item, setItem] = React.useState<StoredFeedbackItem | null>(null);\n const [loading, setLoading] = React.useState(true);\n const [error, setError] = React.useState<string | null>(null);\n\n React.useEffect(() => {\n let cancelled = false;\n setLoading(true);\n setError(null);\n fetchItem(feedbackId)\n .then((data) => { if (!cancelled) setItem(data); })\n .catch((err) => { if (!cancelled) setError(err?.message ?? 'Failed to load item'); })\n .finally(() => { if (!cancelled) setLoading(false); });\n return () => { cancelled = true; };\n }, [feedbackId, fetchItem]);\n\n if (error) {\n return (\n <div ref={ref} className={className}>\n <DetailPage title=\"Feedback\" headerActions={<Button variant=\"outline\" onClick={() => onNavigate('/feedback')}>Back to list</Button>}>\n <div className=\"p-6 text-destructive\">{error}</div>\n </DetailPage>\n </div>\n );\n }\n\n const headerActions = (\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" onClick={() => onNavigate('/feedback')}>Back to list</Button>\n <Button onClick={() => onNavigate(`/feedback/${feedbackId}/edit`)}>Edit</Button>\n </div>\n );\n\n return (\n <div ref={ref} className={className}>\n <DetailPage\n title={item?.content?.slice(0, 50) || `Feedback ${feedbackId.slice(0, 12)}…`}\n headerActions={headerActions}\n loading={loading}\n testId=\"feedback-detail-page\"\n >\n {item && (\n <>\n <DetailSection title=\"Overview\">\n <DetailRow label=\"Status\"><StatusBadge status={item.status} /></DetailRow>\n <DetailRow label=\"Type\"><TypeBadge type={item.type} /></DetailRow>\n <DetailRow label=\"Priority\">{item.priority}</DetailRow>\n <DetailRow label=\"Environment\">{item.environment || '—'}</DetailRow>\n <DetailRow label=\"Submitted\">{item.submittedAt ? new Date(item.submittedAt).toLocaleString() : '—'}</DetailRow>\n <DetailRow label=\"Reporters\">{item.reporterCount ?? 1}</DetailRow>\n {item.assignedTo && <DetailRow label=\"Assigned to\">{item.assignedTo}</DetailRow>}\n {item.scope && <DetailRow label=\"Scope\"><Badge variant=\"outline\">{item.scope}</Badge></DetailRow>}\n {item.size && <DetailRow label=\"Size\"><Badge variant=\"outline\">{item.size}</Badge></DetailRow>}\n </DetailSection>\n\n <DetailSection title=\"Content\">\n <DetailRow label=\"Description\">\n <p className=\"whitespace-pre-wrap\">{item.content}</p>\n </DetailRow>\n </DetailSection>\n\n {item.dialogueHistory && item.dialogueHistory.length > 0 && (\n <DetailSection title=\"Dialogue History\" collapsible defaultOpen={false}>\n {item.dialogueHistory.map((entry, i) => (\n <DetailRow key={i} label={entry.role === 'user' ? 'User' : 'Assistant'}>\n <p className=\"whitespace-pre-wrap text-sm\">{entry.content}</p>\n {entry.timestamp && (\n <span className=\"text-xs text-muted-foreground\">\n {new Date(entry.timestamp).toLocaleString()}\n </span>\n )}\n </DetailRow>\n ))}\n </DetailSection>\n )}\n\n {item.context && (\n <DetailSection title=\"Browser Context\" collapsible defaultOpen={false}>\n {item.context.url && <DetailRow label=\"URL\">{item.context.url}</DetailRow>}\n {item.context.userAgent && <DetailRow label=\"User Agent\">{item.context.userAgent}</DetailRow>}\n {item.context.viewport && <DetailRow label=\"Viewport\">{`${item.context.viewport.width}×${item.context.viewport.height}`}</DetailRow>}\n </DetailSection>\n )}\n\n {item.tags && item.tags.length > 0 && (\n <DetailSection title=\"Tags\">\n <DetailRow label=\"Tags\">\n <div className=\"flex flex-wrap gap-1\">\n {item.tags.map((tag) => <Badge key={tag} variant=\"outline\">{tag}</Badge>)}\n </div>\n </DetailRow>\n </DetailSection>\n )}\n </>\n )}\n </DetailPage>\n </div>\n );\n },\n);\nFeedbackDetailPage.displayName = 'FeedbackDetailPage';\n\nexport { FeedbackDetailPage };\n","/**\n * useStatusTransition — pure hook encoding the FeedbackStatus transition table.\n *\n * Returns the set of valid target statuses reachable from the current status.\n * The current status is excluded from allowedTargets (can't transition to self).\n *\n * No side effects — same inputs always produce same outputs.\n */\nimport type { FeedbackStatus } from '../../../core/types.js';\n\n/**\n * Valid transitions for each FeedbackStatus.\n * open → resolved is deliberately excluded (must go through approved first).\n */\nconst ALLOWED_TRANSITIONS: Record<FeedbackStatus, FeedbackStatus[]> = {\n open: ['approved', 'deferred', 'wontfix'],\n approved: ['open', 'resolved', 'deferred'],\n deferred: ['open', 'approved', 'wontfix'],\n resolved: ['open', 'approved'],\n wontfix: ['open', 'deferred'],\n};\n\nexport interface UseStatusTransitionResult {\n /** Statuses reachable from the current status (current status excluded) */\n allowedTargets: FeedbackStatus[];\n}\n\n/**\n * Returns the allowed target statuses from the given current status.\n *\n * @param currentStatus - The status the item currently has\n * @returns Object with `allowedTargets` — statuses the item can transition to\n *\n * @example\n * const { allowedTargets } = useStatusTransition('open');\n * // allowedTargets → ['approved', 'deferred', 'wontfix']\n */\nexport function useStatusTransition(currentStatus: FeedbackStatus): UseStatusTransitionResult {\n const allowedTargets = ALLOWED_TRANSITIONS[currentStatus] ?? [];\n return { allowedTargets };\n}\n","/**\n * FeedbackUpdatePage.tsx\n * Location: src/react/admin/FeedbackUpdatePage.tsx\n *\n * Admin update page for editing mutable fields of a feedback item.\n * Uses UpdatePage + EnumField + TextField + TextareaField from anton-bakker-tailwind-components.\n */\nimport * as React from 'react';\nimport { UpdatePage, EnumField, TextField, TextareaField, Button, SaveFooter, Badge } from 'anton-bakker-tailwind-components';\nimport type { StoredFeedbackItem, FeedbackStatus, FeedbackType, FeedbackScope, FeedbackSize } from '../../core/types.js';\nimport type { FeedbackUpdatePageProps } from './types.js';\nimport { useStatusTransition } from './hooks/useStatusTransition.js';\n\nconst STATUS_OPTIONS = [\n { value: 'open', label: 'Open' },\n { value: 'approved', label: 'Approved' },\n { value: 'deferred', label: 'Deferred' },\n { value: 'resolved', label: 'Resolved' },\n { value: 'wontfix', label: \"Won't Fix\" },\n];\n\nconst TYPE_OPTIONS = [\n { value: 'bug', label: 'Bug' },\n { value: 'feature', label: 'Feature' },\n { value: 'ux', label: 'UX Issue' },\n { value: 'other', label: 'Other' },\n];\n\nconst SCOPE_OPTIONS = [\n { value: '', label: '— not set —' },\n { value: 'component', label: 'Component' },\n { value: 'page', label: 'Page' },\n { value: 'workflow', label: 'Workflow' },\n];\n\nconst SIZE_OPTIONS = [\n { value: '', label: '— not set —' },\n { value: 'trivial', label: 'Trivial' },\n { value: 'small', label: 'Small' },\n { value: 'medium', label: 'Medium' },\n { value: 'large', label: 'Large' },\n];\n\nconst FeedbackUpdatePage = React.forwardRef<HTMLDivElement, FeedbackUpdatePageProps>(\n ({ feedbackId, fetchItem, updateItem, onNavigate, currentUser, className }, ref) => {\n const [item, setItem] = React.useState<StoredFeedbackItem | null>(null);\n const [loading, setLoading] = React.useState(true);\n const [saving, setSaving] = React.useState(false);\n\n // Form state\n const [status, setStatus] = React.useState<FeedbackStatus>('open');\n const [type, setType] = React.useState<FeedbackType>('bug');\n const [scope, setScope] = React.useState<string>('');\n const [size, setSize] = React.useState<string>('');\n const [assignedTo, setAssignedTo] = React.useState('');\n const [tags, setTags] = React.useState('');\n const [correctionNote, setCorrectionNote] = React.useState('');\n\n // Load item\n React.useEffect(() => {\n let cancelled = false;\n setLoading(true);\n fetchItem(feedbackId)\n .then((data) => {\n if (cancelled) return;\n setItem(data);\n setStatus(data.status);\n setType(data.type);\n setScope(data.scope ?? '');\n setSize(data.size ?? '');\n setAssignedTo(data.assignedTo ?? '');\n setTags(data.tags?.join(', ') ?? '');\n })\n .finally(() => { if (!cancelled) setLoading(false); });\n return () => { cancelled = true; };\n }, [feedbackId, fetchItem]);\n\n const { allowedTargets } = useStatusTransition(status);\n const statusOptions = STATUS_OPTIONS.filter(\n (opt) => opt.value === status || allowedTargets.includes(opt.value as FeedbackStatus),\n );\n\n // Dirty detection\n const classificationChanged = item && (type !== item.type || scope !== (item.scope ?? '') || size !== (item.size ?? ''));\n const isDirty = item && (\n status !== item.status ||\n type !== item.type ||\n scope !== (item.scope ?? '') ||\n size !== (item.size ?? '') ||\n assignedTo !== (item.assignedTo ?? '') ||\n tags !== (item.tags?.join(', ') ?? '')\n );\n\n const handleSave = async () => {\n if (!item) return;\n setSaving(true);\n try {\n const patch: Partial<StoredFeedbackItem> = { lastModifiedAt: new Date().toISOString() };\n if (status !== item.status) patch.status = status;\n if (type !== item.type) patch.type = type;\n if (scope !== (item.scope ?? '')) (patch as Record<string, unknown>).scope = scope || undefined;\n if (size !== (item.size ?? '')) (patch as Record<string, unknown>).size = size || undefined;\n if (assignedTo !== (item.assignedTo ?? '')) (patch as Record<string, unknown>).assignedTo = assignedTo || undefined;\n const newTags = tags.split(',').map((t) => t.trim()).filter(Boolean);\n if (JSON.stringify(newTags) !== JSON.stringify(item.tags ?? [])) patch.tags = newTags;\n\n if (status === 'resolved' && item.status !== 'resolved') {\n patch.resolvedAt = new Date().toISOString();\n patch.resolvedBy = currentUser?.userId ?? 'unknown';\n }\n\n if (classificationChanged && correctionNote) {\n patch.corrections = [\n ...(item.corrections ?? []),\n {\n field: [type !== item.type && 'type', scope !== (item.scope ?? '') && 'scope', size !== (item.size ?? '') && 'size'].filter(Boolean).join(', '),\n before: { type: item.type, scope: item.scope, size: item.size },\n after: { type, scope: scope || undefined, size: size || undefined },\n correctedBy: currentUser?.userId ?? 'unknown',\n correctedAt: new Date().toISOString(),\n reason: correctionNote,\n },\n ];\n }\n\n await updateItem(feedbackId, patch);\n onNavigate(`/feedback/${feedbackId}`);\n } finally {\n setSaving(false);\n }\n };\n\n const headerActions = (\n <Button variant=\"outline\" onClick={() => onNavigate(`/feedback/${feedbackId}`)}>\n Cancel\n </Button>\n );\n\n return (\n <div ref={ref} className={className}>\n <UpdatePage\n title={`Edit: ${item?.content?.slice(0, 40) || feedbackId.slice(0, 12)}…`}\n headerActions={headerActions}\n dataLoading={loading}\n actions={\n <SaveFooter dirty={!!isDirty} saving={saving}>\n <Button variant=\"outline\" onClick={() => onNavigate(`/feedback/${feedbackId}`)}>Cancel</Button>\n <Button\n onClick={handleSave}\n disabled={saving || !isDirty || (!!classificationChanged && !correctionNote)}\n >\n {saving ? 'Saving…' : 'Save changes'}\n </Button>\n </SaveFooter>\n }\n testId=\"feedback-update-page\"\n >\n {item && (\n <div className=\"space-y-6\">\n {/* Read-only context */}\n <div className=\"rounded-lg border border-border p-4 bg-muted/30\">\n <p className=\"text-sm text-muted-foreground mb-2\">Original submission</p>\n <p className=\"whitespace-pre-wrap text-sm\">{item.content}</p>\n <div className=\"flex gap-3 mt-3 text-xs text-muted-foreground\">\n <span>{item.submittedAt ? new Date(item.submittedAt).toLocaleString() : ''}</span>\n <span>{item.environment}</span>\n <span>{item.reporterCount ?? 1} reporter(s)</span>\n </div>\n </div>\n\n {/* Editable fields */}\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <EnumField\n label=\"Status\"\n options={statusOptions}\n value={status}\n onChange={(v) => setStatus(v as FeedbackStatus)}\n />\n <EnumField\n label=\"Type\"\n options={TYPE_OPTIONS}\n value={type}\n onChange={(v) => setType(v as FeedbackType)}\n />\n <EnumField\n label=\"Scope\"\n options={SCOPE_OPTIONS}\n value={scope}\n onChange={setScope}\n />\n <EnumField\n label=\"Size\"\n options={SIZE_OPTIONS}\n value={size}\n onChange={setSize}\n />\n <TextField\n label=\"Assigned to\"\n value={assignedTo}\n onChange={(e) => setAssignedTo(e.target.value)}\n placeholder={currentUser?.name ?? currentUser?.userId ?? ''}\n {...(currentUser ? { description: `You: ${currentUser.name ?? currentUser.userId}` } : {})}\n />\n <TextField\n label=\"Tags\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n placeholder=\"Comma-separated tags\"\n description=\"Separate multiple tags with commas\"\n />\n </div>\n\n {/* Correction note (required when classification changes) */}\n {classificationChanged && (\n <TextareaField\n label=\"Correction note\"\n value={correctionNote}\n onChange={(e) => setCorrectionNote(e.target.value)}\n required\n {...(!correctionNote ? { error: 'Required when changing type, scope, or size' } : {})}\n placeholder=\"Explain why this classification is being changed...\"\n />\n )}\n\n {/* Existing tags display */}\n {item.tags && item.tags.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {item.tags.map((tag) => <Badge key={tag} variant=\"outline\">{tag}</Badge>)}\n </div>\n )}\n </div>\n )}\n </UpdatePage>\n </div>\n );\n },\n);\nFeedbackUpdatePage.displayName = 'FeedbackUpdatePage';\n\nexport { FeedbackUpdatePage };\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/react/admin/FeedbackListPage.tsx","../../../src/react/admin/FeedbackDetailPage.tsx","../../../src/react/admin/hooks/useStatusTransition.ts","../../../src/react/admin/FeedbackUpdatePage.tsx"],"sourcesContent":["/**\n * FeedbackListPage.tsx\n * Location: src/react/admin/FeedbackListPage.tsx\n *\n * Admin list page for browsing and filtering feedback items.\n * Thin wrapper around ListPage from anton-bakker-tailwind-components\n * for visual consistency with sagrosoft20.\n */\nimport * as React from 'react';\nimport { ListPage, Badge } from 'anton-bakker-tailwind-components';\nimport type { SimpleColumn, FilterBarProps } from 'anton-bakker-tailwind-components';\nimport type { StoredFeedbackItem, FeedbackStatus, FeedbackType } from '../../core/types.js';\nimport type { FeedbackListPageProps } from './types.js';\n\nconst STATUS_VARIANT: Record<FeedbackStatus, 'default' | 'success' | 'warning' | 'destructive' | 'secondary'> = {\n open: 'warning',\n approved: 'default',\n deferred: 'secondary',\n resolved: 'success',\n wontfix: 'destructive',\n};\n\nconst TYPE_VARIANT: Record<FeedbackType, 'default' | 'success' | 'warning' | 'destructive' | 'secondary'> = {\n bug: 'destructive',\n feature: 'default',\n ux: 'warning',\n other: 'secondary',\n};\n\nfunction StatusBadge({ status }: { status: FeedbackStatus }) {\n return <Badge variant={STATUS_VARIANT[status] ?? 'secondary'}>{status}</Badge>;\n}\n\nfunction TypeBadge({ type }: { type: FeedbackType }) {\n return <Badge variant={TYPE_VARIANT[type] ?? 'secondary'}>{type}</Badge>;\n}\n\nconst FeedbackListPage = React.forwardRef<HTMLDivElement, FeedbackListPageProps>(\n ({ items, loading = false, onNavigate, className, emptyStateMessage, currentUser, ...rest }, ref) => {\n const [search, setSearch] = React.useState('');\n const [statusFilter, setStatusFilter] = React.useState('');\n const [typeFilter, setTypeFilter] = React.useState('');\n\n const filteredItems = React.useMemo(() => {\n let result = items;\n if (statusFilter) result = result.filter((i) => i.status === statusFilter);\n if (typeFilter) result = result.filter((i) => i.type === typeFilter);\n if (search) {\n const q = search.toLowerCase();\n result = result.filter((i) => i.content?.toLowerCase().includes(q) || i.id.toLowerCase().includes(q));\n }\n return result;\n }, [items, statusFilter, typeFilter, search]);\n\n const columns: SimpleColumn<StoredFeedbackItem>[] = React.useMemo(() => [\n { header: 'Content', accessor: (row) => row.content?.slice(0, 60) || row.id, sortable: true },\n { header: 'Type', accessor: (row) => <TypeBadge type={row.type} />, sortable: true },\n { header: 'Status', accessor: (row) => <StatusBadge status={row.status} />, sortable: true },\n { header: 'Priority', accessor: 'priority', sortable: true },\n { header: 'Reporters', accessor: 'reporterCount', sortable: true },\n {\n header: 'Date',\n accessor: (row) => row.submittedAt ? new Date(row.submittedAt).toLocaleDateString() : '—',\n sortable: true,\n },\n ], []);\n\n const filterBar: FilterBarProps | undefined = (statusFilter || typeFilter) ? {\n filters: [\n ...(statusFilter ? [{ field: 'status', label: `Status: ${statusFilter}`, value: statusFilter }] : []),\n ...(typeFilter ? [{ field: 'type', label: `Type: ${typeFilter}`, value: typeFilter }] : []),\n ],\n onFilterRemove: (field: string) => {\n if (field === 'status') setStatusFilter('');\n if (field === 'type') setTypeFilter('');\n },\n onFiltersClear: () => { setStatusFilter(''); setTypeFilter(''); },\n } : undefined;\n\n const headerActions = (\n <div className=\"flex items-center gap-2\">\n <select\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value)}\n className=\"h-8 rounded-md border border-border bg-transparent px-2 text-sm\"\n aria-label=\"Filter by status\"\n >\n <option value=\"\">All statuses</option>\n <option value=\"open\">Open</option>\n <option value=\"approved\">Approved</option>\n <option value=\"deferred\">Deferred</option>\n <option value=\"resolved\">Resolved</option>\n <option value=\"wontfix\">Won't fix</option>\n </select>\n <select\n value={typeFilter}\n onChange={(e) => setTypeFilter(e.target.value)}\n className=\"h-8 rounded-md border border-border bg-transparent px-2 text-sm\"\n aria-label=\"Filter by type\"\n >\n <option value=\"\">All types</option>\n <option value=\"bug\">Bug</option>\n <option value=\"feature\">Feature</option>\n <option value=\"ux\">UX</option>\n <option value=\"other\">Other</option>\n </select>\n </div>\n );\n\n const listProps = {\n title: 'Feedback' as const,\n columns,\n items: filteredItems,\n loading,\n onRowClick: (row: StoredFeedbackItem) => onNavigate(`/feedback/${row.id}`),\n search,\n onSearchChange: setSearch,\n searchPlaceholder: 'Search feedback...',\n headerActions,\n testId: 'feedback-list-page',\n ...(filterBar ? { filterBar } : {}),\n ...(emptyStateMessage ? { emptyState: emptyStateMessage } : {}),\n ...(className ? { className } : {}),\n };\n\n return <ListPage<StoredFeedbackItem> ref={ref} {...listProps} />;\n },\n);\nFeedbackListPage.displayName = 'FeedbackListPage';\n\nexport { FeedbackListPage, StatusBadge, TypeBadge };\n","/**\n * FeedbackDetailPage.tsx\n * Location: src/react/admin/FeedbackDetailPage.tsx\n *\n * Admin detail page for viewing a single feedback item.\n * Renders structured content (bug reports, feature requests) with all fields,\n * screenshot, console errors, and dialogue history.\n */\nimport * as React from 'react';\nimport { DetailPage, DetailSection, DetailRow, Badge, Button } from 'anton-bakker-tailwind-components';\nimport type { StoredFeedbackItem } from '../../core/types.js';\nimport type { FeedbackDetailPageProps } from './types.js';\nimport { StatusBadge, TypeBadge } from './FeedbackListPage.js';\n\n/**\n * Parse structured content (produced by serializeManualForm or AI dialogue)\n * into labeled key-value pairs.\n * Format: \"Label: value\" lines, with optional [Bug Report] or [Feature Request] header.\n */\nfunction parseStructuredContent(content: string): { header: string | undefined; fields: { label: string; value: string }[]; raw: string } {\n const lines = content.split('\\n');\n let header: string | undefined;\n const fields: { label: string; value: string }[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n // Check for header line like [Bug Report] or [Feature Request]\n const headerMatch = trimmed.match(/^\\[(.+)\\]$/);\n if (headerMatch && headerMatch[1]) {\n header = headerMatch[1];\n continue;\n }\n\n // Check for \"Label: value\" pattern\n const fieldMatch = trimmed.match(/^([A-Z][A-Za-z\\s]+):\\s*(.+)$/);\n if (fieldMatch && fieldMatch[1] && fieldMatch[2]) {\n fields.push({ label: fieldMatch[1], value: fieldMatch[2] });\n }\n }\n\n return { header, fields, raw: content };\n}\n\nconst FeedbackDetailPage = React.forwardRef<HTMLDivElement, FeedbackDetailPageProps>(\n ({ feedbackId, fetchItem, getScreenshotUrl, onNavigate, currentUser, className }, ref) => {\n const [item, setItem] = React.useState<StoredFeedbackItem | null>(null);\n const [loading, setLoading] = React.useState(true);\n const [error, setError] = React.useState<string | null>(null);\n const [screenshotUrl, setScreenshotUrl] = React.useState<string | null>(null);\n\n React.useEffect(() => {\n let cancelled = false;\n setLoading(true);\n setError(null);\n fetchItem(feedbackId)\n .then((data) => { if (!cancelled) setItem(data); })\n .catch((err) => { if (!cancelled) setError(err?.message ?? 'Failed to load item'); })\n .finally(() => { if (!cancelled) setLoading(false); });\n return () => { cancelled = true; };\n }, [feedbackId, fetchItem]);\n\n // Load screenshot URL when item has one\n React.useEffect(() => {\n if (item?.hasScreenshot && item.screenshotS3Key && getScreenshotUrl) {\n getScreenshotUrl(item.screenshotS3Key)\n .then(setScreenshotUrl)\n .catch(() => setScreenshotUrl(null));\n }\n }, [item?.hasScreenshot, item?.screenshotS3Key, getScreenshotUrl]);\n\n if (error) {\n return (\n <div ref={ref} className={className}>\n <DetailPage title=\"Feedback\" headerActions={<Button variant=\"outline\" onClick={() => onNavigate('/feedback')}>Back to list</Button>}>\n <div className=\"p-6 text-destructive\">{error}</div>\n </DetailPage>\n </div>\n );\n }\n\n const headerActions = (\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" onClick={() => onNavigate('/feedback')}>Back to list</Button>\n <Button onClick={() => onNavigate(`/feedback/${feedbackId}/edit`)}>Edit</Button>\n </div>\n );\n\n // Parse structured content\n const parsed = item ? parseStructuredContent(item.content) : null;\n const hasStructuredFields = parsed && parsed.fields.length > 0;\n\n return (\n <div ref={ref} className={className}>\n <DetailPage\n title={item?.content?.split('\\n').find(l => l.match(/^Summary:/))?.replace('Summary: ', '') || parsed?.header || `Feedback ${feedbackId.slice(0, 12)}…`}\n headerActions={headerActions}\n loading={loading}\n testId=\"feedback-detail-page\"\n >\n {item && (\n <>\n <DetailSection title=\"Overview\">\n <DetailRow label=\"Status\"><StatusBadge status={item.status} /></DetailRow>\n <DetailRow label=\"Type\"><TypeBadge type={item.type} /></DetailRow>\n <DetailRow label=\"Priority\">{item.priority}</DetailRow>\n <DetailRow label=\"Environment\">{item.environment || '—'}</DetailRow>\n <DetailRow label=\"Submitted\">{item.submittedAt ? new Date(item.submittedAt).toLocaleString() : '—'}</DetailRow>\n <DetailRow label=\"Reporters\">{item.reporterCount ?? 1}</DetailRow>\n {item.assignedTo && <DetailRow label=\"Assigned to\">{item.assignedTo}</DetailRow>}\n {item.scope && <DetailRow label=\"Scope\"><Badge variant=\"outline\">{item.scope}</Badge></DetailRow>}\n {item.size && <DetailRow label=\"Size\"><Badge variant=\"outline\">{item.size}</Badge></DetailRow>}\n </DetailSection>\n\n <DetailSection title=\"Content\">\n {hasStructuredFields ? (\n // Render each structured field as a separate row\n parsed.fields.map((field, i) => (\n <DetailRow key={i} label={field.label}>\n <p className=\"whitespace-pre-wrap\">{field.value}</p>\n </DetailRow>\n ))\n ) : (\n // Fallback: render raw content\n <DetailRow label=\"Description\">\n <p className=\"whitespace-pre-wrap\">{item.content}</p>\n </DetailRow>\n )}\n </DetailSection>\n\n {/* Screenshot */}\n {item.hasScreenshot && (\n <DetailSection title=\"Screenshot\">\n <DetailRow label=\"Capture\">\n {screenshotUrl ? (\n <a href={screenshotUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n <img\n src={screenshotUrl}\n alt=\"Feedback screenshot\"\n className=\"max-w-full max-h-[400px] rounded border cursor-pointer hover:opacity-90 transition-opacity\"\n />\n </a>\n ) : (\n <span className=\"text-muted-foreground text-sm\">\n {getScreenshotUrl ? 'Loading screenshot…' : 'Screenshot available (no viewer configured)'}\n </span>\n )}\n </DetailRow>\n </DetailSection>\n )}\n\n {/* Console errors */}\n {item.context?.consoleErrors && item.context.consoleErrors.length > 0 && (\n <DetailSection title=\"Console Errors\" collapsible defaultOpen>\n <DetailRow label={`${item.context.consoleErrors.length} error(s)`}>\n <ul className=\"list-none space-y-1\">\n {item.context.consoleErrors.map((err, i) => (\n <li key={i} className=\"font-mono text-xs text-destructive bg-destructive/5 p-2 rounded\">\n {err}\n </li>\n ))}\n </ul>\n </DetailRow>\n </DetailSection>\n )}\n\n {/* Dialogue history */}\n {item.dialogueHistory && item.dialogueHistory.length > 0 && (\n <DetailSection title=\"Dialogue History\" collapsible defaultOpen={false}>\n {item.dialogueHistory.map((entry, i) => (\n <DetailRow key={i} label={entry.role === 'user' ? 'User' : 'Assistant'}>\n <p className=\"whitespace-pre-wrap text-sm\">{entry.content}</p>\n {entry.timestamp && (\n <span className=\"text-xs text-muted-foreground\">\n {new Date(entry.timestamp).toLocaleString()}\n </span>\n )}\n </DetailRow>\n ))}\n </DetailSection>\n )}\n\n {/* Browser context */}\n {item.context && (\n <DetailSection title=\"Browser Context\" collapsible defaultOpen={false}>\n {item.context.url && <DetailRow label=\"URL\">{item.context.url}</DetailRow>}\n {item.context.pageTitle && <DetailRow label=\"Page\">{item.context.pageTitle}</DetailRow>}\n {item.context.userAgent && <DetailRow label=\"User Agent\">{item.context.userAgent}</DetailRow>}\n {item.context.viewport && <DetailRow label=\"Viewport\">{`${item.context.viewport.width}×${item.context.viewport.height}`}</DetailRow>}\n {item.context.locale && <DetailRow label=\"Locale\">{item.context.locale}</DetailRow>}\n {item.context.timezone && <DetailRow label=\"Timezone\">{item.context.timezone}</DetailRow>}\n {item.context.performanceMs != null && <DetailRow label=\"Page Load\">{`${item.context.performanceMs}ms`}</DetailRow>}\n </DetailSection>\n )}\n\n {/* Tags */}\n {item.tags && item.tags.length > 0 && (\n <DetailSection title=\"Tags\">\n <DetailRow label=\"Tags\">\n <div className=\"flex flex-wrap gap-1\">\n {item.tags.map((tag) => <Badge key={tag} variant=\"outline\">{tag}</Badge>)}\n </div>\n </DetailRow>\n </DetailSection>\n )}\n </>\n )}\n </DetailPage>\n </div>\n );\n },\n);\nFeedbackDetailPage.displayName = 'FeedbackDetailPage';\n\nexport { FeedbackDetailPage };\n","/**\n * useStatusTransition — pure hook encoding the FeedbackStatus transition table.\n *\n * Returns the set of valid target statuses reachable from the current status.\n * The current status is excluded from allowedTargets (can't transition to self).\n *\n * No side effects — same inputs always produce same outputs.\n */\nimport type { FeedbackStatus } from '../../../core/types.js';\n\n/**\n * Valid transitions for each FeedbackStatus.\n * open → resolved is deliberately excluded (must go through approved first).\n */\nconst ALLOWED_TRANSITIONS: Record<FeedbackStatus, FeedbackStatus[]> = {\n open: ['approved', 'deferred', 'wontfix'],\n approved: ['open', 'resolved', 'deferred'],\n deferred: ['open', 'approved', 'wontfix'],\n resolved: ['open', 'approved'],\n wontfix: ['open', 'deferred'],\n};\n\nexport interface UseStatusTransitionResult {\n /** Statuses reachable from the current status (current status excluded) */\n allowedTargets: FeedbackStatus[];\n}\n\n/**\n * Returns the allowed target statuses from the given current status.\n *\n * @param currentStatus - The status the item currently has\n * @returns Object with `allowedTargets` — statuses the item can transition to\n *\n * @example\n * const { allowedTargets } = useStatusTransition('open');\n * // allowedTargets → ['approved', 'deferred', 'wontfix']\n */\nexport function useStatusTransition(currentStatus: FeedbackStatus): UseStatusTransitionResult {\n const allowedTargets = ALLOWED_TRANSITIONS[currentStatus] ?? [];\n return { allowedTargets };\n}\n","/**\n * FeedbackUpdatePage.tsx\n * Location: src/react/admin/FeedbackUpdatePage.tsx\n *\n * Admin update page for editing mutable fields of a feedback item.\n * Uses UpdatePage + EnumField + TextField + TextareaField from anton-bakker-tailwind-components.\n */\nimport * as React from 'react';\nimport { UpdatePage, EnumField, TextField, TextareaField, Button, SaveFooter, Badge } from 'anton-bakker-tailwind-components';\nimport type { StoredFeedbackItem, FeedbackStatus, FeedbackType, FeedbackScope, FeedbackSize } from '../../core/types.js';\nimport type { FeedbackUpdatePageProps } from './types.js';\nimport { useStatusTransition } from './hooks/useStatusTransition.js';\n\nconst STATUS_OPTIONS = [\n { value: 'open', label: 'Open' },\n { value: 'approved', label: 'Approved' },\n { value: 'deferred', label: 'Deferred' },\n { value: 'resolved', label: 'Resolved' },\n { value: 'wontfix', label: \"Won't Fix\" },\n];\n\nconst TYPE_OPTIONS = [\n { value: 'bug', label: 'Bug' },\n { value: 'feature', label: 'Feature' },\n { value: 'ux', label: 'UX Issue' },\n { value: 'other', label: 'Other' },\n];\n\nconst SCOPE_OPTIONS = [\n { value: '', label: '— not set —' },\n { value: 'component', label: 'Component' },\n { value: 'page', label: 'Page' },\n { value: 'workflow', label: 'Workflow' },\n];\n\nconst SIZE_OPTIONS = [\n { value: '', label: '— not set —' },\n { value: 'trivial', label: 'Trivial' },\n { value: 'small', label: 'Small' },\n { value: 'medium', label: 'Medium' },\n { value: 'large', label: 'Large' },\n];\n\nconst FeedbackUpdatePage = React.forwardRef<HTMLDivElement, FeedbackUpdatePageProps>(\n ({ feedbackId, fetchItem, updateItem, onNavigate, currentUser, className }, ref) => {\n const [item, setItem] = React.useState<StoredFeedbackItem | null>(null);\n const [loading, setLoading] = React.useState(true);\n const [saving, setSaving] = React.useState(false);\n\n // Form state\n const [status, setStatus] = React.useState<FeedbackStatus>('open');\n const [type, setType] = React.useState<FeedbackType>('bug');\n const [scope, setScope] = React.useState<string>('');\n const [size, setSize] = React.useState<string>('');\n const [assignedTo, setAssignedTo] = React.useState('');\n const [tags, setTags] = React.useState('');\n const [correctionNote, setCorrectionNote] = React.useState('');\n\n // Load item\n React.useEffect(() => {\n let cancelled = false;\n setLoading(true);\n fetchItem(feedbackId)\n .then((data) => {\n if (cancelled) return;\n setItem(data);\n setStatus(data.status);\n setType(data.type);\n setScope(data.scope ?? '');\n setSize(data.size ?? '');\n setAssignedTo(data.assignedTo ?? '');\n setTags(data.tags?.join(', ') ?? '');\n })\n .finally(() => { if (!cancelled) setLoading(false); });\n return () => { cancelled = true; };\n }, [feedbackId, fetchItem]);\n\n const { allowedTargets } = useStatusTransition(status);\n const statusOptions = STATUS_OPTIONS.filter(\n (opt) => opt.value === status || allowedTargets.includes(opt.value as FeedbackStatus),\n );\n\n // Dirty detection\n const classificationChanged = item && (type !== item.type || scope !== (item.scope ?? '') || size !== (item.size ?? ''));\n const isDirty = item && (\n status !== item.status ||\n type !== item.type ||\n scope !== (item.scope ?? '') ||\n size !== (item.size ?? '') ||\n assignedTo !== (item.assignedTo ?? '') ||\n tags !== (item.tags?.join(', ') ?? '')\n );\n\n const handleSave = async () => {\n if (!item) return;\n setSaving(true);\n try {\n const patch: Partial<StoredFeedbackItem> = { lastModifiedAt: new Date().toISOString() };\n if (status !== item.status) patch.status = status;\n if (type !== item.type) patch.type = type;\n if (scope !== (item.scope ?? '')) (patch as Record<string, unknown>).scope = scope || undefined;\n if (size !== (item.size ?? '')) (patch as Record<string, unknown>).size = size || undefined;\n if (assignedTo !== (item.assignedTo ?? '')) (patch as Record<string, unknown>).assignedTo = assignedTo || undefined;\n const newTags = tags.split(',').map((t) => t.trim()).filter(Boolean);\n if (JSON.stringify(newTags) !== JSON.stringify(item.tags ?? [])) patch.tags = newTags;\n\n if (status === 'resolved' && item.status !== 'resolved') {\n patch.resolvedAt = new Date().toISOString();\n patch.resolvedBy = currentUser?.userId ?? 'unknown';\n }\n\n if (classificationChanged && correctionNote) {\n patch.corrections = [\n ...(item.corrections ?? []),\n {\n field: [type !== item.type && 'type', scope !== (item.scope ?? '') && 'scope', size !== (item.size ?? '') && 'size'].filter(Boolean).join(', '),\n before: { type: item.type, scope: item.scope, size: item.size },\n after: { type, scope: scope || undefined, size: size || undefined },\n correctedBy: currentUser?.userId ?? 'unknown',\n correctedAt: new Date().toISOString(),\n reason: correctionNote,\n },\n ];\n }\n\n await updateItem(feedbackId, patch);\n onNavigate(`/feedback/${feedbackId}`);\n } finally {\n setSaving(false);\n }\n };\n\n const headerActions = (\n <Button variant=\"outline\" onClick={() => onNavigate(`/feedback/${feedbackId}`)}>\n Cancel\n </Button>\n );\n\n return (\n <div ref={ref} className={className}>\n <UpdatePage\n title={`Edit: ${item?.content?.slice(0, 40) || feedbackId.slice(0, 12)}…`}\n headerActions={headerActions}\n dataLoading={loading}\n actions={\n <SaveFooter dirty={!!isDirty} saving={saving}>\n <Button variant=\"outline\" onClick={() => onNavigate(`/feedback/${feedbackId}`)}>Cancel</Button>\n <Button\n onClick={handleSave}\n disabled={saving || !isDirty || (!!classificationChanged && !correctionNote)}\n >\n {saving ? 'Saving…' : 'Save changes'}\n </Button>\n </SaveFooter>\n }\n testId=\"feedback-update-page\"\n >\n {item && (\n <div className=\"space-y-6\">\n {/* Read-only context */}\n <div className=\"rounded-lg border border-border p-4 bg-muted/30\">\n <p className=\"text-sm text-muted-foreground mb-2\">Original submission</p>\n <p className=\"whitespace-pre-wrap text-sm\">{item.content}</p>\n <div className=\"flex gap-3 mt-3 text-xs text-muted-foreground\">\n <span>{item.submittedAt ? new Date(item.submittedAt).toLocaleString() : ''}</span>\n <span>{item.environment}</span>\n <span>{item.reporterCount ?? 1} reporter(s)</span>\n </div>\n </div>\n\n {/* Editable fields */}\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <EnumField\n label=\"Status\"\n options={statusOptions}\n value={status}\n onChange={(v) => setStatus(v as FeedbackStatus)}\n />\n <EnumField\n label=\"Type\"\n options={TYPE_OPTIONS}\n value={type}\n onChange={(v) => setType(v as FeedbackType)}\n />\n <EnumField\n label=\"Scope\"\n options={SCOPE_OPTIONS}\n value={scope}\n onChange={setScope}\n />\n <EnumField\n label=\"Size\"\n options={SIZE_OPTIONS}\n value={size}\n onChange={setSize}\n />\n <TextField\n label=\"Assigned to\"\n value={assignedTo}\n onChange={(e) => setAssignedTo(e.target.value)}\n placeholder={currentUser?.name ?? currentUser?.userId ?? ''}\n {...(currentUser ? { description: `You: ${currentUser.name ?? currentUser.userId}` } : {})}\n />\n <TextField\n label=\"Tags\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n placeholder=\"Comma-separated tags\"\n description=\"Separate multiple tags with commas\"\n />\n </div>\n\n {/* Correction note (required when classification changes) */}\n {classificationChanged && (\n <TextareaField\n label=\"Correction note\"\n value={correctionNote}\n onChange={(e) => setCorrectionNote(e.target.value)}\n required\n {...(!correctionNote ? { error: 'Required when changing type, scope, or size' } : {})}\n placeholder=\"Explain why this classification is being changed...\"\n />\n )}\n\n {/* Existing tags display */}\n {item.tags && item.tags.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {item.tags.map((tag) => <Badge key={tag} variant=\"outline\">{tag}</Badge>)}\n </div>\n )}\n </div>\n )}\n </UpdatePage>\n </div>\n );\n },\n);\nFeedbackUpdatePage.displayName = 'FeedbackUpdatePage';\n\nexport { FeedbackUpdatePage };\n"],"names":[],"mappings":"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"}
|
|
@@ -30,6 +30,8 @@ export interface FeedbackListPageProps extends AdminPageProps {
|
|
|
30
30
|
export interface FeedbackDetailPageProps extends AdminPageProps {
|
|
31
31
|
feedbackId: string;
|
|
32
32
|
fetchItem: (id: string) => Promise<StoredFeedbackItem>;
|
|
33
|
+
/** Optional callback to get a displayable URL for the screenshot (e.g. presigned S3 URL) */
|
|
34
|
+
getScreenshotUrl?: (s3Key: string) => Promise<string>;
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
37
|
* Props for the FeedbackUpdatePage component.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/react/admin/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/react/admin/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvD,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACvD;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvD,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anton-bakker-feedback",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Zero-dependency business logic layer for feedback collection — types, capture, transport, config, i18n, observability",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,11 @@
|
|
|
39
39
|
"test": "vitest",
|
|
40
40
|
"test:ci": "vitest run",
|
|
41
41
|
"lint": "eslint src --max-warnings 0",
|
|
42
|
-
"clean": "rm -rf dist"
|
|
42
|
+
"clean": "rm -rf dist",
|
|
43
|
+
"prepublishOnly": "npm run build",
|
|
44
|
+
"publish:patch": "npm run build && npm version patch && npm publish",
|
|
45
|
+
"publish:minor": "npm run build && npm version minor && npm publish",
|
|
46
|
+
"publish:major": "npm run build && npm version major && npm publish"
|
|
43
47
|
},
|
|
44
48
|
"peerDependencies": {
|
|
45
49
|
"anton-bakker-tailwind-components": ">=0.7.0",
|