anton-bakker-feedback 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{i18n-RpKqSWAT.js → i18n-Du-or3T4.js} +3 -102
- package/dist/chunks/i18n-Du-or3T4.js.map +1 -0
- package/dist/chunks/observability-CfwHKObh.js +102 -0
- package/dist/chunks/observability-CfwHKObh.js.map +1 -0
- package/dist/chunks/submitResolver-DsfkrUHY.js +133 -0
- package/dist/chunks/submitResolver-DsfkrUHY.js.map +1 -0
- package/dist/config/database.d.ts +76 -0
- package/dist/config/database.d.ts.map +1 -0
- package/dist/core/formTypes.d.ts +65 -0
- package/dist/core/formTypes.d.ts.map +1 -0
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/models/StoredFeedbackItem.d.ts +79 -0
- package/dist/models/StoredFeedbackItem.d.ts.map +1 -0
- package/dist/react/admin/FeedbackDetailPage.d.ts +7 -25
- package/dist/react/admin/FeedbackDetailPage.d.ts.map +1 -1
- package/dist/react/admin/FeedbackListPage.d.ts +20 -0
- package/dist/react/admin/FeedbackListPage.d.ts.map +1 -0
- package/dist/react/admin/FeedbackUpdatePage.d.ts +8 -30
- package/dist/react/admin/FeedbackUpdatePage.d.ts.map +1 -1
- package/dist/react/admin/__tests__/FeedbackListPage.test.d.ts.map +1 -0
- package/dist/react/admin/index.d.ts +3 -2
- package/dist/react/admin/index.d.ts.map +1 -1
- package/dist/react/admin/index.js +352 -1259
- package/dist/react/admin/index.js.map +1 -1
- package/dist/react/admin/types.d.ts +20 -14
- package/dist/react/admin/types.d.ts.map +1 -1
- package/dist/react.js +9 -136
- package/dist/react.js.map +1 -1
- package/dist/server/FeedbackService.d.ts +32 -0
- package/dist/server/FeedbackService.d.ts.map +1 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/listHandler.d.ts +17 -0
- package/dist/server/listHandler.d.ts.map +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +202 -1
- package/dist/server.js.map +1 -1
- package/dist/types/context.d.ts +92 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/feedback.d.ts +30 -0
- package/dist/types/feedback.d.ts.map +1 -0
- package/package.json +7 -1
- package/dist/chunks/i18n-RpKqSWAT.js.map +0 -1
- package/dist/react/admin/__tests__/FeedbackUpdatePage.integration.test.d.ts +0 -2
- package/dist/react/admin/__tests__/FeedbackUpdatePage.integration.test.d.ts.map +0 -1
- package/dist/react/admin/components/BrowserContext.d.ts +0 -32
- package/dist/react/admin/components/BrowserContext.d.ts.map +0 -1
- package/dist/react/admin/components/DataTable.d.ts +0 -55
- package/dist/react/admin/components/DataTable.d.ts.map +0 -1
- package/dist/react/admin/components/DialogueHistory.d.ts +0 -27
- package/dist/react/admin/components/DialogueHistory.d.ts.map +0 -1
- package/dist/react/admin/components/EditableFields.d.ts +0 -126
- package/dist/react/admin/components/EditableFields.d.ts.map +0 -1
- package/dist/react/admin/components/FeedbackListPage.d.ts +0 -19
- package/dist/react/admin/components/FeedbackListPage.d.ts.map +0 -1
- package/dist/react/admin/components/FeedbackListPage.test.d.ts.map +0 -1
- package/dist/react/admin/components/FilterControls.d.ts +0 -28
- package/dist/react/admin/components/FilterControls.d.ts.map +0 -1
- package/dist/react/admin/components/NavigationLink.d.ts +0 -31
- package/dist/react/admin/components/NavigationLink.d.ts.map +0 -1
- package/dist/react/admin/components/QuickActions.d.ts +0 -43
- package/dist/react/admin/components/QuickActions.d.ts.map +0 -1
- package/dist/react/admin/components/ScreenshotViewer.d.ts +0 -38
- package/dist/react/admin/components/ScreenshotViewer.d.ts.map +0 -1
- package/dist/react/admin/components/SortControls.d.ts +0 -29
- package/dist/react/admin/components/SortControls.d.ts.map +0 -1
- package/dist/react/admin/components/StatusBadge.d.ts +0 -23
- package/dist/react/admin/components/StatusBadge.d.ts.map +0 -1
- package/dist/react/admin/components/TypeBadge.d.ts +0 -23
- package/dist/react/admin/components/TypeBadge.d.ts.map +0 -1
- package/dist/react/admin/components/__tests__/BrowserContext.test.d.ts +0 -2
- package/dist/react/admin/components/__tests__/BrowserContext.test.d.ts.map +0 -1
- package/dist/react/admin/components/__tests__/DialogueHistory.test.d.ts +0 -2
- package/dist/react/admin/components/__tests__/DialogueHistory.test.d.ts.map +0 -1
- package/dist/react/admin/components/__tests__/EditableFields.test.d.ts +0 -2
- package/dist/react/admin/components/__tests__/EditableFields.test.d.ts.map +0 -1
- package/dist/react/admin/components/__tests__/QuickActions.test.d.ts +0 -2
- package/dist/react/admin/components/__tests__/QuickActions.test.d.ts.map +0 -1
- package/dist/react/admin/components/__tests__/ScreenshotViewer.test.d.ts +0 -2
- package/dist/react/admin/components/__tests__/ScreenshotViewer.test.d.ts.map +0 -1
- package/dist/react/admin/components/__tests__/badges.test.d.ts +0 -2
- package/dist/react/admin/components/__tests__/badges.test.d.ts.map +0 -1
- package/dist/react/admin/components/__tests__/controls.test.d.ts +0 -2
- package/dist/react/admin/components/__tests__/controls.test.d.ts.map +0 -1
- package/dist/react/admin/components/__tests__/table-navigation.test.d.ts +0 -2
- package/dist/react/admin/components/__tests__/table-navigation.test.d.ts.map +0 -1
- package/dist/react/admin/components/index.d.ts +0 -13
- package/dist/react/admin/components/index.d.ts.map +0 -1
- package/dist/react/admin/hooks/useFeedbackFilters.d.ts +0 -31
- package/dist/react/admin/hooks/useFeedbackFilters.d.ts.map +0 -1
- package/dist/react/admin/hooks/useFeedbackSort.d.ts +0 -42
- package/dist/react/admin/hooks/useFeedbackSort.d.ts.map +0 -1
- /package/dist/react/admin/{components → __tests__}/FeedbackListPage.test.d.ts +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/react/admin/hooks/useFeedbackFilters.ts","../../../src/react/admin/hooks/useFeedbackSort.ts","../../../src/react/admin/components/StatusBadge.tsx","../../../src/react/admin/components/TypeBadge.tsx","../../../src/react/admin/components/FilterControls.tsx","../../../src/react/admin/components/FeedbackListPage.tsx","../../../src/react/admin/components/DialogueHistory.tsx","../../../src/react/admin/components/ScreenshotViewer.tsx","../../../src/react/admin/components/BrowserContext.tsx","../../../src/react/admin/components/QuickActions.tsx","../../../src/react/admin/FeedbackDetailPage.tsx","../../../src/react/admin/hooks/useStatusTransition.ts","../../../src/react/admin/components/EditableFields.tsx","../../../src/react/admin/FeedbackUpdatePage.tsx"],"sourcesContent":["// src/react/admin/hooks/useFeedbackFilters.ts\nimport { useState, useCallback } from 'react';\nimport type { StoredFeedbackItem } from '../../../core/types.js';\nimport type { AdminFilterState } from '../types.js';\n\n/** Default (empty) filter state — all filters off */\nconst DEFAULT_FILTERS: AdminFilterState = {\n status: '',\n type: '',\n environment: '',\n};\n\n/**\n * Result returned by `useFeedbackFilters`.\n */\nexport interface UseFeedbackFiltersResult {\n /** Current filter state */\n filters: AdminFilterState;\n /** Update a single filter or replace the entire filter state */\n setFilters: (filters: AdminFilterState) => void;\n /** Reset all filters to their default (empty) values */\n clearFilters: () => void;\n /**\n * Apply the current filters to an items array.\n * Filters are ANDed — an item must pass all active filters to be included.\n */\n applyFilters: (items: StoredFeedbackItem[]) => StoredFeedbackItem[];\n}\n\n/**\n * useFeedbackFilters — manages filter state for the admin feedback list.\n *\n * Filters use AND logic: status × type × environment must all match.\n * An empty string value ('') for a filter means \"show all\" for that dimension.\n *\n * @example\n * const { filters, setFilters, clearFilters, applyFilters } = useFeedbackFilters();\n * const visibleItems = applyFilters(items);\n */\nexport function useFeedbackFilters(): UseFeedbackFiltersResult {\n const [filters, setFiltersState] = useState<AdminFilterState>(DEFAULT_FILTERS);\n\n const setFilters = useCallback((next: AdminFilterState) => {\n setFiltersState(next);\n }, []);\n\n const clearFilters = useCallback(() => {\n setFiltersState(DEFAULT_FILTERS);\n }, []);\n\n const applyFilters = useCallback(\n (items: StoredFeedbackItem[]): StoredFeedbackItem[] => {\n return items.filter((item) => {\n // Status filter\n if (filters.status !== '' && item.status !== filters.status) {\n return false;\n }\n // Type filter\n if (filters.type !== '' && item.type !== filters.type) {\n return false;\n }\n // Environment filter\n if (filters.environment !== '' && item.environment !== filters.environment) {\n return false;\n }\n return true;\n });\n },\n [filters]\n );\n\n return { filters, setFilters, clearFilters, applyFilters };\n}\n","// src/react/admin/hooks/useFeedbackSort.ts\nimport { useState, useCallback } from 'react';\nimport type { StoredFeedbackItem } from '../../../core/types.js';\nimport type { AdminSortState } from '../types.js';\n\n/** Default sort state — priority descending */\nconst DEFAULT_SORT: AdminSortState = {\n field: 'priority',\n direction: 'desc',\n};\n\n/**\n * Result returned by `useFeedbackSort`.\n */\nexport interface UseFeedbackSortResult {\n /** Current sort state */\n sort: AdminSortState;\n /**\n * Set the sort field.\n * If the same field is already sorted, toggles direction.\n * If a new field is set, defaults to descending.\n */\n setSortField: (field: AdminSortState['field']) => void;\n /** Directly set the full sort state */\n setSort: (sort: AdminSortState) => void;\n /**\n * Apply current sort to an items array and return a new sorted array.\n * Does not mutate the input.\n */\n applySort: (items: StoredFeedbackItem[]) => StoredFeedbackItem[];\n}\n\n/**\n * Compare function for string values — handles undefined gracefully.\n */\nfunction compareStrings(a: string | undefined, b: string | undefined, direction: 'asc' | 'desc'): number {\n const strA = a ?? '';\n const strB = b ?? '';\n const cmp = strA.localeCompare(strB);\n return direction === 'asc' ? cmp : -cmp;\n}\n\n/**\n * Compare function for numeric values — handles undefined gracefully.\n */\nfunction compareNumbers(a: number | undefined, b: number | undefined, direction: 'asc' | 'desc'): number {\n const numA = a ?? 0;\n const numB = b ?? 0;\n const cmp = numA - numB;\n return direction === 'asc' ? cmp : -cmp;\n}\n\n/**\n * Compare function for ISO 8601 date strings — handles undefined gracefully.\n */\nfunction compareDates(a: string | undefined, b: string | undefined, direction: 'asc' | 'desc'): number {\n const timeA = a ? new Date(a).getTime() : 0;\n const timeB = b ? new Date(b).getTime() : 0;\n const cmp = timeA - timeB;\n return direction === 'asc' ? cmp : -cmp;\n}\n\n/**\n * Sort a list of feedback items by the given sort state.\n * Returns a new array — does not mutate the input.\n */\nexport function sortFeedbackItems(\n items: StoredFeedbackItem[],\n sort: AdminSortState\n): StoredFeedbackItem[] {\n const { field, direction } = sort;\n\n return [...items].sort((a, b) => {\n switch (field) {\n case 'priority':\n return compareNumbers(a.priority, b.priority, direction);\n\n case 'submittedAt':\n return compareDates(a.submittedAt, b.submittedAt, direction);\n\n case 'status':\n return compareStrings(a.status, b.status, direction);\n\n case 'type':\n return compareStrings(a.type, b.type, direction);\n\n case 'reporterCount':\n return compareNumbers(a.reporterCount, b.reporterCount, direction);\n\n default:\n return 0;\n }\n });\n}\n\n/**\n * useFeedbackSort — manages sort state for the admin feedback list.\n *\n * Default sort: priority descending (highest priority items first).\n *\n * Clicking the same column header toggles ascending/descending.\n * Clicking a different column header resets to descending for that column.\n *\n * @example\n * const { sort, setSortField, applySort } = useFeedbackSort();\n * const sortedItems = applySort(filteredItems);\n */\nexport function useFeedbackSort(): UseFeedbackSortResult {\n const [sort, setSortState] = useState<AdminSortState>(DEFAULT_SORT);\n\n const setSortField = useCallback((field: AdminSortState['field']) => {\n setSortState((prev) => {\n if (prev.field === field) {\n // Same field — toggle direction\n return { field, direction: prev.direction === 'asc' ? 'desc' : 'asc' };\n }\n // New field — default to descending\n return { field, direction: 'desc' };\n });\n }, []);\n\n const setSort = useCallback((next: AdminSortState) => {\n setSortState(next);\n }, []);\n\n const applySort = useCallback(\n (items: StoredFeedbackItem[]): StoredFeedbackItem[] => sortFeedbackItems(items, sort),\n [sort]\n );\n\n return { sort, setSortField, setSort, applySort };\n}\n","import React from 'react';\nimport type { FeedbackStatus } from '../../../core/types.js';\n\n/**\n * Props for the StatusBadge component.\n */\nexport interface StatusBadgeProps {\n /** The feedback status value to display */\n status: FeedbackStatus;\n /** Additional CSS class names */\n className?: string;\n}\n\n/** Human-readable labels for each FeedbackStatus value */\nconst STATUS_LABELS: Record<FeedbackStatus, string> = {\n open: 'Open',\n approved: 'Approved',\n deferred: 'Deferred',\n resolved: 'Resolved',\n wontfix: \"Won't fix\",\n};\n\n/**\n * StatusBadge — headless badge component for rendering a FeedbackStatus.\n *\n * Renders a `<span>` with a `data-status` attribute for CSS targeting.\n * No styles applied — consumer provides styling via `className` prop.\n *\n * @example\n * <StatusBadge status=\"open\" className=\"badge badge--open\" />\n */\nexport function StatusBadge({ status, className }: StatusBadgeProps): React.JSX.Element {\n return (\n <span className={className} data-status={status}>\n {STATUS_LABELS[status] ?? status}\n </span>\n );\n}\n","import React from 'react';\nimport type { FeedbackType } from '../../../core/types.js';\n\n/**\n * Props for the TypeBadge component.\n */\nexport interface TypeBadgeProps {\n /** The feedback type value to display */\n type: FeedbackType;\n /** Additional CSS class names */\n className?: string;\n}\n\n/** Human-readable labels for each FeedbackType value */\nconst TYPE_LABELS: Record<FeedbackType, string> = {\n bug: 'Bug',\n feature: 'Feature',\n ux: 'UX issue',\n other: 'Other',\n};\n\n/**\n * TypeBadge — headless badge component for rendering a FeedbackType.\n *\n * Renders a `<span>` with a `data-type` attribute for CSS targeting.\n * No styles applied — consumer provides styling via `className` prop.\n *\n * @example\n * <TypeBadge type=\"bug\" className=\"badge badge--bug\" />\n */\nexport function TypeBadge({ type, className }: TypeBadgeProps): React.JSX.Element {\n return (\n <span className={className} data-type={type}>\n {TYPE_LABELS[type] ?? type}\n </span>\n );\n}\n","import React from 'react';\nimport type { FeedbackStatus, FeedbackType } from '../../../core/types.js';\nimport type { AdminFilterState } from '../types.js';\n\n/**\n * Props for the FilterControls component.\n */\nexport interface FilterControlsProps {\n /** Current filter state */\n filters: AdminFilterState;\n /** Callback fired when any filter value changes */\n onChange: (filters: AdminFilterState) => void;\n /** Additional CSS class names */\n className?: string;\n}\n\nconst STATUS_OPTIONS: Array<{ value: FeedbackStatus | ''; label: string }> = [\n { value: '', label: 'All' },\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: Array<{ value: FeedbackType | ''; label: string }> = [\n { value: '', label: 'All' },\n { value: 'bug', label: 'Bug' },\n { value: 'feature', label: 'Feature' },\n { value: 'ux', label: 'UX issue' },\n { value: 'other', label: 'Other' },\n];\n\nconst ENVIRONMENT_OPTIONS = [\n { value: '', label: 'All' },\n { value: 'development', label: 'Development' },\n { value: 'staging', label: 'Staging' },\n { value: 'production', label: 'Production' },\n];\n\n/**\n * FilterControls — headless filtering controls for feedback items.\n *\n * Renders `<select>` elements for status, type, and environment filters.\n * No direct state management — emits changes via `onChange` callback.\n *\n * @example\n * <FilterControls\n * filters={{ status: '', type: '', environment: '' }}\n * onChange={(f) => setFilters(f)}\n * />\n */\nexport function FilterControls({ filters, onChange, className }: FilterControlsProps): React.JSX.Element {\n const statusId = 'feedback-admin-filter-status';\n const typeId = 'feedback-admin-filter-type';\n const environmentId = 'feedback-admin-filter-environment';\n\n const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {\n onChange({ ...filters, status: e.target.value as FeedbackStatus | '' });\n };\n\n const handleTypeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {\n onChange({ ...filters, type: e.target.value as FeedbackType | '' });\n };\n\n const handleEnvironmentChange = (e: React.ChangeEvent<HTMLSelectElement>) => {\n onChange({ ...filters, environment: e.target.value });\n };\n\n return (\n <div className={className} role=\"search\" aria-label=\"Filter feedback items\">\n <div className=\"feedback-admin-filter__group\">\n <label htmlFor={statusId}>Status</label>\n <select\n id={statusId}\n value={filters.status}\n onChange={handleStatusChange}\n aria-label=\"Filter by status\"\n aria-describedby={`${statusId}-desc`}\n >\n {STATUS_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <span id={`${statusId}-desc`} className=\"feedback-admin-sr-only\">\n Filter feedback items by lifecycle status\n </span>\n </div>\n\n <div className=\"feedback-admin-filter__group\">\n <label htmlFor={typeId}>Type</label>\n <select\n id={typeId}\n value={filters.type}\n onChange={handleTypeChange}\n aria-label=\"Filter by type\"\n aria-describedby={`${typeId}-desc`}\n >\n {TYPE_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <span id={`${typeId}-desc`} className=\"feedback-admin-sr-only\">\n Filter feedback items by type\n </span>\n </div>\n\n <div className=\"feedback-admin-filter__group\">\n <label htmlFor={environmentId}>Environment</label>\n <select\n id={environmentId}\n value={filters.environment}\n onChange={handleEnvironmentChange}\n aria-label=\"Filter by environment\"\n aria-describedby={`${environmentId}-desc`}\n >\n {ENVIRONMENT_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <span id={`${environmentId}-desc`} className=\"feedback-admin-sr-only\">\n Filter feedback items by environment\n </span>\n </div>\n </div>\n );\n}\n","// src/react/admin/components/FeedbackListPage.tsx\nimport React from 'react';\nimport type { StoredFeedbackItem } from '../../../core/types.js';\nimport type { FeedbackListPageProps } from '../types.js';\nimport { useFeedbackFilters } from '../hooks/useFeedbackFilters.js';\nimport { useFeedbackSort } from '../hooks/useFeedbackSort.js';\nimport { StatusBadge } from './StatusBadge.js';\nimport { TypeBadge } from './TypeBadge.js';\nimport { FilterControls } from './FilterControls.js';\n\n// Re-export the props type for consumers\nexport type { FeedbackListPageProps };\n\n/**\n * Format an ISO 8601 date string to a short localised date.\n */\nfunction formatDate(iso: string): string {\n try {\n return new Date(iso).toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n } catch {\n return iso;\n }\n}\n\n/**\n * Sort icon rendered inside column headers.\n * Shows up/down arrows, with the active direction highlighted.\n */\nfunction SortIndicator({\n active,\n direction,\n className,\n}: {\n active: boolean;\n direction: 'asc' | 'desc';\n className?: string;\n}): React.JSX.Element {\n if (!active) {\n return (\n <span className={className} aria-hidden=\"true\">\n {' ↕'}\n </span>\n );\n }\n return (\n <span className={className} aria-hidden=\"true\">\n {direction === 'asc' ? ' ↑' : ' ↓'}\n </span>\n );\n}\n\n/**\n * FeedbackListPage — admin list page for browsing and filtering feedback items.\n *\n * Headless/unstyled component using Inversion of Control pattern.\n * Consumer provides data via `items` prop; this component manages\n * filter and sort state internally.\n *\n * @example\n * <FeedbackListPage\n * items={myItems}\n * onNavigate={(path) => router.push(path)}\n * />\n */\nexport function FeedbackListPage({\n items,\n loading = false,\n onNavigate,\n className,\n tableClassName,\n filterClassName,\n emptyStateMessage = 'No feedback items found.',\n}: FeedbackListPageProps): React.JSX.Element {\n const { filters, setFilters, clearFilters, applyFilters } = useFeedbackFilters();\n const { sort, setSortField, applySort } = useFeedbackSort();\n\n // Apply filters then sort\n const visibleItems: StoredFeedbackItem[] = applySort(applyFilters(items));\n\n /**\n * Build props for a sortable column header <th>.\n */\n function thProps(field: typeof sort.field) {\n return {\n scope: 'col' as const,\n className: `feedback-admin-list__th feedback-admin-list__th--sortable${sort.field === field ? ' feedback-admin-list__th--sorted' : ''}`,\n onClick: () => setSortField(field),\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setSortField(field);\n }\n },\n tabIndex: 0,\n role: 'columnheader' as const,\n 'aria-sort': (sort.field === field\n ? sort.direction === 'asc'\n ? 'ascending'\n : 'descending'\n : 'none') as 'ascending' | 'descending' | 'none',\n };\n }\n\n const renderBody = (): React.ReactNode => {\n if (loading) {\n return (\n <tr>\n <td\n colSpan={7}\n className=\"feedback-admin-list__loading\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n >\n Loading…\n </td>\n </tr>\n );\n }\n\n if (visibleItems.length === 0) {\n return (\n <tr>\n <td colSpan={7} className=\"feedback-admin-list__empty\" aria-live=\"polite\">\n {emptyStateMessage}\n </td>\n </tr>\n );\n }\n\n return visibleItems.map((item) => (\n <tr\n key={item.id}\n className=\"feedback-admin-list__row feedback-admin-list__row--clickable\"\n onClick={() => onNavigate(`/feedback/${item.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onNavigate(`/feedback/${item.id}`);\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-label={`View feedback item ${item.id}`}\n >\n <td className=\"feedback-admin-list__td feedback-admin-list__td--title\">\n {item.content}\n </td>\n <td className=\"feedback-admin-list__td feedback-admin-list__td--type\">\n <TypeBadge\n type={item.type}\n className={`feedback-admin-badge feedback-admin-badge--type feedback-admin-badge--${item.type}`}\n />\n </td>\n <td className=\"feedback-admin-list__td feedback-admin-list__td--status\">\n <StatusBadge\n status={item.status}\n className={`feedback-admin-badge feedback-admin-badge--status feedback-admin-badge--${item.status}`}\n />\n </td>\n <td className=\"feedback-admin-list__td feedback-admin-list__td--priority\">\n {item.priority}\n </td>\n <td className=\"feedback-admin-list__td feedback-admin-list__td--reporters\">\n {item.reporterCount.toLocaleString()}\n </td>\n <td className=\"feedback-admin-list__td feedback-admin-list__td--environment\">\n {item.environment ?? '—'}\n </td>\n <td className=\"feedback-admin-list__td feedback-admin-list__td--date\">\n {formatDate(item.submittedAt)}\n </td>\n </tr>\n ));\n };\n\n return (\n <div className={`feedback-admin-list${className ? ` ${className}` : ''}`}>\n {/* Filter controls */}\n <div className={`feedback-admin-list__filters${filterClassName ? ` ${filterClassName}` : ''}`}>\n <FilterControls\n filters={filters}\n onChange={setFilters}\n className=\"feedback-admin-filter-controls\"\n />\n <button\n type=\"button\"\n className=\"feedback-admin-list__clear-btn\"\n onClick={clearFilters}\n aria-label=\"Clear all filters\"\n >\n Clear filters\n </button>\n </div>\n\n {/* Table */}\n <div className={`feedback-admin-list__table-wrapper${tableClassName ? ` ${tableClassName}` : ''}`}>\n <table className=\"feedback-admin-list__table\" role=\"grid\" aria-label=\"Feedback items\">\n <thead className=\"feedback-admin-list__thead\">\n <tr>\n <th\n scope=\"col\"\n className=\"feedback-admin-list__th\"\n >\n Title\n </th>\n <th {...thProps('type')}>\n Type\n <SortIndicator\n active={sort.field === 'type'}\n direction={sort.direction}\n className=\"feedback-admin-list__sort-icon\"\n />\n </th>\n <th {...thProps('status')}>\n Status\n <SortIndicator\n active={sort.field === 'status'}\n direction={sort.direction}\n className=\"feedback-admin-list__sort-icon\"\n />\n </th>\n <th {...thProps('priority')}>\n Priority\n <SortIndicator\n active={sort.field === 'priority'}\n direction={sort.direction}\n className=\"feedback-admin-list__sort-icon\"\n />\n </th>\n <th {...thProps('reporterCount')}>\n Reporters\n <SortIndicator\n active={sort.field === 'reporterCount'}\n direction={sort.direction}\n className=\"feedback-admin-list__sort-icon\"\n />\n </th>\n <th\n scope=\"col\"\n className=\"feedback-admin-list__th\"\n >\n Environment\n </th>\n <th {...thProps('submittedAt')}>\n Date\n <SortIndicator\n active={sort.field === 'submittedAt'}\n direction={sort.direction}\n className=\"feedback-admin-list__sort-icon\"\n />\n </th>\n </tr>\n </thead>\n <tbody className=\"feedback-admin-list__tbody\">{renderBody()}</tbody>\n </table>\n </div>\n </div>\n );\n}\n","import React from 'react';\nimport type { DialogueEntry } from '../../../core/types.js';\n\n/**\n * Props for the DialogueHistory component.\n */\nexport interface DialogueHistoryProps {\n /**\n * Array of dialogue turns between user and assistant.\n * Rendered in chronological order (index 0 first).\n */\n dialogue: DialogueEntry[];\n /** Additional CSS class names applied to the root element */\n className?: string;\n}\n\n/**\n * DialogueHistory — headless conversation thread display component.\n *\n * Renders dialogue turns in chronological order with semantic distinction\n * between user and assistant messages. No styling applied — consumer provides\n * CSS via `className` prop.\n *\n * @example\n * <DialogueHistory dialogue={item.dialogueHistory} className=\"dialogue-thread\" />\n */\nexport function DialogueHistory({\n dialogue,\n className,\n}: DialogueHistoryProps): React.JSX.Element {\n if (!dialogue || dialogue.length === 0) {\n return (\n <div className={className} data-testid=\"dialogue-history-empty\">\n <p className=\"feedback-admin-dialogue__empty\">No dialogue recorded.</p>\n </div>\n );\n }\n\n return (\n <div className={className} data-testid=\"dialogue-history\">\n <ol className=\"feedback-admin-dialogue__list\" aria-label=\"Dialogue history\">\n {dialogue.map((entry, index) => {\n // Guard against malformed entries\n if (!entry || typeof entry !== 'object') {\n return null;\n }\n\n const role = entry.role === 'assistant' ? 'assistant' : 'user';\n const content =\n typeof entry.content === 'string' ? entry.content : '';\n const timestamp =\n typeof entry.timestamp === 'string' ? entry.timestamp : '';\n\n let displayTime = '';\n if (timestamp) {\n try {\n displayTime = new Date(timestamp).toLocaleString();\n } catch {\n displayTime = timestamp;\n }\n }\n\n return (\n <li\n key={`dialogue-${index}`}\n className=\"feedback-admin-dialogue__turn\"\n data-role={role}\n >\n <header className=\"feedback-admin-dialogue__turn-header\">\n <span\n className=\"feedback-admin-dialogue__turn-role\"\n aria-label={`${role} message`}\n >\n {role === 'user' ? 'User' : 'Assistant'}\n </span>\n {displayTime && (\n <time\n className=\"feedback-admin-dialogue__turn-time\"\n dateTime={timestamp}\n >\n {displayTime}\n </time>\n )}\n </header>\n <div className=\"feedback-admin-dialogue__turn-content\">\n {content.split('\\n').map((line, lineIndex) => (\n <React.Fragment key={lineIndex}>\n {line}\n {lineIndex < content.split('\\n').length - 1 && <br />}\n </React.Fragment>\n ))}\n </div>\n </li>\n );\n })}\n </ol>\n </div>\n );\n}\n","import React, { useEffect, useState } from 'react';\n\n/**\n * Props for the ScreenshotViewer component.\n */\nexport interface ScreenshotViewerProps {\n /**\n * S3 key for the screenshot. When provided, `generateScreenshotUrl` is\n * called to obtain a display URL. When absent or null, a fallback message\n * is shown.\n */\n screenshotS3Key?: string | null;\n /**\n * Consumer-provided function that resolves an S3 key to a displayable URL.\n * If not provided and a key exists, a configuration hint is shown.\n */\n generateScreenshotUrl?: (s3Key: string) => Promise<string>;\n /** Alt text for the screenshot image. Defaults to \"Feedback screenshot\". */\n alt?: string;\n /** Additional CSS class names applied to the root element */\n className?: string;\n}\n\n/**\n * ScreenshotViewer — headless screenshot display component.\n *\n * Displays a screenshot image with accessibility attributes. Degrades\n * gracefully when no screenshot is available or the URL resolver is not\n * configured. No styling applied — consumer provides CSS via `className` prop.\n *\n * @example\n * <ScreenshotViewer\n * screenshotS3Key={item.screenshotS3Key}\n * generateScreenshotUrl={myUrlResolver}\n * className=\"screenshot-panel\"\n * />\n */\nexport function ScreenshotViewer({\n screenshotS3Key,\n generateScreenshotUrl,\n alt = 'Feedback screenshot',\n className,\n}: ScreenshotViewerProps): React.JSX.Element {\n const [imageUrl, setImageUrl] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!screenshotS3Key) {\n setImageUrl(null);\n setLoading(false);\n setError(null);\n return;\n }\n\n if (!generateScreenshotUrl) {\n setImageUrl(null);\n setLoading(false);\n setError(null);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n generateScreenshotUrl(screenshotS3Key)\n .then((url) => {\n setImageUrl(url);\n setLoading(false);\n })\n .catch(() => {\n setImageUrl(null);\n setLoading(false);\n setError('Failed to load screenshot.');\n });\n }, [screenshotS3Key, generateScreenshotUrl]);\n\n return (\n <div className={className} data-testid=\"screenshot-viewer\">\n {loading && (\n <p className=\"feedback-admin-screenshot__loading\">Loading screenshot…</p>\n )}\n\n {!loading && imageUrl && (\n <img\n src={imageUrl}\n alt={alt}\n className=\"feedback-admin-screenshot__image\"\n />\n )}\n\n {!loading && !imageUrl && screenshotS3Key && !generateScreenshotUrl && (\n <p className=\"feedback-admin-screenshot__configure\">\n Configure generateScreenshotUrl to display screenshot.\n </p>\n )}\n\n {!loading && !imageUrl && screenshotS3Key && generateScreenshotUrl && error && (\n <p className=\"feedback-admin-screenshot__error\">{error}</p>\n )}\n\n {!loading && !screenshotS3Key && (\n <p className=\"feedback-admin-screenshot__missing\">\n No screenshot captured.\n </p>\n )}\n </div>\n );\n}\n","import React from 'react';\nimport type { FeedbackContext } from '../../../core/types.js';\n\n/**\n * Props for the BrowserContext component.\n */\nexport interface BrowserContextProps {\n /**\n * The feedback context object captured at submission time.\n * May be undefined, null, or partial — component handles all missing fields\n * gracefully.\n *\n * Accept both `null` and `undefined` so consumers can pass either without\n * TypeScript errors (with exactOptionalPropertyTypes enabled).\n */\n context?: FeedbackContext | null;\n /** Additional CSS class names applied to the root element */\n className?: string;\n}\n\n/**\n * BrowserContext — headless browser context display component.\n *\n * Renders a structured, readable summary of the captured browser context\n * including the page URL, user agent, and viewport dimensions. Handles\n * undefined/null gracefully. No styling applied — consumer provides CSS\n * via `className` prop.\n *\n * @example\n * <BrowserContext context={item.context} className=\"browser-context-panel\" />\n */\nexport function BrowserContext({\n context,\n className,\n}: BrowserContextProps): React.JSX.Element {\n if (!context) {\n return (\n <div className={className} data-testid=\"browser-context-empty\">\n <p className=\"feedback-admin-context__empty\">\n No browser context available.\n </p>\n </div>\n );\n }\n\n const { url, pageTitle, userAgent, viewport, locale, timezone } = context;\n\n return (\n <div className={className} data-testid=\"browser-context\">\n <dl className=\"feedback-admin-context__list\">\n {url && (\n <>\n <dt className=\"feedback-admin-context__term\">URL</dt>\n <dd className=\"feedback-admin-context__value feedback-admin-context__value--url\">\n {url}\n </dd>\n </>\n )}\n\n {pageTitle && (\n <>\n <dt className=\"feedback-admin-context__term\">Page title</dt>\n <dd className=\"feedback-admin-context__value\">{pageTitle}</dd>\n </>\n )}\n\n {userAgent && (\n <>\n <dt className=\"feedback-admin-context__term\">User agent</dt>\n <dd className=\"feedback-admin-context__value feedback-admin-context__value--user-agent\">\n {userAgent}\n </dd>\n </>\n )}\n\n {viewport && (\n <>\n <dt className=\"feedback-admin-context__term\">Viewport</dt>\n <dd className=\"feedback-admin-context__value\">\n {viewport.width} × {viewport.height}\n </dd>\n </>\n )}\n\n {locale && (\n <>\n <dt className=\"feedback-admin-context__term\">Locale</dt>\n <dd className=\"feedback-admin-context__value\">{locale}</dd>\n </>\n )}\n\n {timezone && (\n <>\n <dt className=\"feedback-admin-context__term\">Timezone</dt>\n <dd className=\"feedback-admin-context__value\">{timezone}</dd>\n </>\n )}\n </dl>\n </div>\n );\n}\n","import React, { useState } from 'react';\nimport type { StoredFeedbackItem, FeedbackStatus } from '../../../core/types.js';\n\n/**\n * Allowed status transitions from each status.\n * Note: open → resolved is intentionally blocked (must go through approved first).\n */\nconst ALLOWED_TRANSITIONS: Record<FeedbackStatus, FeedbackStatus[]> = {\n open: ['approved', 'deferred', 'wontfix'],\n approved: ['resolved', 'deferred', 'wontfix'],\n deferred: ['open', 'approved', 'wontfix'],\n resolved: ['open'],\n wontfix: ['open'],\n};\n\n/** Human-readable labels for status transition buttons */\nconst STATUS_TRANSITION_LABELS: Record<FeedbackStatus, string> = {\n open: 'Reopen',\n approved: 'Approve',\n deferred: 'Defer',\n resolved: 'Resolve',\n wontfix: \"Won't fix\",\n};\n\n/**\n * Props for the QuickActions component.\n */\nexport interface QuickActionsProps {\n /** The feedback item to act on */\n item: StoredFeedbackItem;\n /**\n * Called when a status transition is requested.\n * Consumer owns the data mutation.\n */\n updateItem: (\n id: string,\n patch: Partial<StoredFeedbackItem>\n ) => Promise<void>;\n /** Called when a navigation action is triggered */\n onNavigate: (path: string) => void;\n /** Optional current user for displaying user-specific actions */\n currentUser?: {\n userId: string;\n name?: string;\n };\n /** Additional CSS class names applied to the root element */\n className?: string;\n}\n\n/**\n * QuickActions — headless action button group for common feedback operations.\n *\n * Renders status transition buttons based on allowed transitions for the\n * current status, plus navigation buttons for editing and returning to the list.\n * Buttons are disabled during async update operations. No styling applied —\n * consumer provides CSS via `className` prop.\n *\n * @example\n * <QuickActions\n * item={feedbackItem}\n * updateItem={updateItem}\n * onNavigate={navigate}\n * currentUser={currentUser}\n * className=\"quick-actions-bar\"\n * />\n */\nexport function QuickActions({\n item,\n updateItem,\n onNavigate,\n className,\n}: QuickActionsProps): React.JSX.Element {\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const allowedTargets = ALLOWED_TRANSITIONS[item.status] ?? [];\n\n const handleStatusTransition = async (targetStatus: FeedbackStatus) => {\n setSaving(true);\n setError(null);\n try {\n await updateItem(item.id, { status: targetStatus });\n } catch {\n setError('Failed to update status. Please try again.');\n } finally {\n setSaving(false);\n }\n };\n\n const handleEdit = () => {\n onNavigate(`/feedback/${item.id}/edit`);\n };\n\n const handleBackToList = () => {\n onNavigate('/feedback');\n };\n\n return (\n <div className={className} data-testid=\"quick-actions\">\n <div className=\"feedback-admin-actions__status-group\" role=\"group\" aria-label=\"Status actions\">\n {allowedTargets.map((targetStatus) => (\n <button\n key={targetStatus}\n type=\"button\"\n className=\"feedback-admin-actions__status-btn\"\n data-target-status={targetStatus}\n onClick={() => void handleStatusTransition(targetStatus)}\n disabled={saving}\n aria-disabled={saving}\n >\n {STATUS_TRANSITION_LABELS[targetStatus]}\n </button>\n ))}\n </div>\n\n {error && (\n <p\n className=\"feedback-admin-actions__error\"\n role=\"alert\"\n data-testid=\"quick-actions-error\"\n >\n {error}\n </p>\n )}\n\n <div className=\"feedback-admin-actions__nav-group\">\n <button\n type=\"button\"\n className=\"feedback-admin-actions__edit-btn\"\n onClick={handleEdit}\n disabled={saving}\n aria-disabled={saving}\n data-testid=\"quick-actions-edit\"\n >\n Edit\n </button>\n\n <button\n type=\"button\"\n className=\"feedback-admin-actions__back-btn\"\n onClick={handleBackToList}\n disabled={saving}\n aria-disabled={saving}\n data-testid=\"quick-actions-back\"\n >\n Back to list\n </button>\n </div>\n </div>\n );\n}\n","import React, { useEffect, useState } from 'react';\nimport type { StoredFeedbackItem } from '../../core/types.js';\nimport type { FeedbackDetailPageProps } from './types.js';\nimport { StatusBadge } from './components/StatusBadge.js';\nimport { TypeBadge } from './components/TypeBadge.js';\nimport { DialogueHistory } from './components/DialogueHistory.js';\nimport { ScreenshotViewer } from './components/ScreenshotViewer.js';\nimport { BrowserContext } from './components/BrowserContext.js';\nimport { QuickActions } from './components/QuickActions.js';\n\n/**\n * FeedbackDetailPage — headless single-item feedback detail component.\n *\n * Uses Inversion of Control: consumer provides data fetching and navigation\n * via props. The component owns loading/error state and rendering logic only.\n *\n * Renders:\n * - Header: title (content truncated), type badge, status badge, priority\n * - Metadata grid: environment, submitted date, reporter count, assignee\n * - Description / full content\n * - Dialogue history (conversation turns)\n * - Screenshot viewer\n * - Browser context\n * - Quick actions (status transitions, edit, back to list)\n *\n * No styling applied — consumer provides CSS via `className` prop.\n *\n * @example\n * <FeedbackDetailPage\n * feedbackId=\"FB-2026-07-28-abc12345\"\n * fetchItem={(id) => api.getFeedback(id)}\n * onNavigate={(path) => router.push(path)}\n * currentUser={{ userId: 'user-1', name: 'Alice' }}\n * />\n */\nexport function FeedbackDetailPage({\n feedbackId,\n fetchItem,\n onNavigate,\n currentUser,\n className,\n generateScreenshotUrl,\n}: FeedbackDetailPageProps): React.JSX.Element {\n const [item, setItem] = useState<StoredFeedbackItem | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n setLoading(true);\n setError(null);\n\n fetchItem(feedbackId)\n .then((fetched) => {\n if (!cancelled) {\n setItem(fetched);\n setLoading(false);\n }\n })\n .catch(() => {\n if (!cancelled) {\n setError('Failed to load feedback item.');\n setLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [feedbackId, fetchItem]);\n\n // ── Loading state ──────────────────────────────────────────────\n if (loading) {\n return (\n <div\n className={className}\n data-testid=\"feedback-detail-loading\"\n aria-busy=\"true\"\n aria-label=\"Loading feedback item\"\n >\n <p className=\"feedback-admin-detail__loading\">Loading…</p>\n </div>\n );\n }\n\n // ── Error state ────────────────────────────────────────────────\n if (error || !item) {\n return (\n <div\n className={className}\n data-testid=\"feedback-detail-error\"\n role=\"alert\"\n >\n <p className=\"feedback-admin-detail__error\">\n {error ?? 'Feedback item not found.'}\n </p>\n <button\n type=\"button\"\n className=\"feedback-admin-detail__back-btn\"\n onClick={() => onNavigate('/feedback')}\n data-testid=\"feedback-detail-back-error\"\n >\n Back to list\n </button>\n </div>\n );\n }\n\n // ── Handle updateItem for QuickActions ─────────────────────────\n // FeedbackDetailPage does not own the update function, so we provide\n // a no-op that re-fetches after an optimistic update for the QuickActions.\n // The QuickActions component receives updateItem as a prop — here we\n // wrap fetchItem refresh after a successful update.\n const handleUpdateItem = async (\n id: string,\n patch: Partial<StoredFeedbackItem>\n ): Promise<void> => {\n // Optimistically update local state\n setItem((prev) => (prev ? { ...prev, ...patch } : prev));\n // Re-fetch to sync server state — consumer fetchItem is the source of truth\n try {\n const updated = await fetchItem(id);\n setItem(updated);\n } catch {\n // If re-fetch fails, keep optimistic state; QuickActions shows error\n }\n };\n\n const formatDate = (isoString: string): string => {\n try {\n return new Date(isoString).toLocaleString();\n } catch {\n return isoString;\n }\n };\n\n return (\n <article\n className={className}\n data-testid=\"feedback-detail\"\n data-feedback-id={item.id}\n >\n {/* ── Header ───────────────────────────────────────────── */}\n <header className=\"feedback-admin-detail__header\">\n <h1 className=\"feedback-admin-detail__title\">\n {item.content.length > 120\n ? `${item.content.slice(0, 120)}…`\n : item.content}\n </h1>\n\n <div className=\"feedback-admin-detail__badges\">\n <TypeBadge\n type={item.type}\n className=\"feedback-admin-detail__type-badge\"\n />\n <StatusBadge\n status={item.status}\n className=\"feedback-admin-detail__status-badge\"\n />\n <span\n className=\"feedback-admin-detail__priority\"\n data-priority={item.priority}\n aria-label={`Priority: ${item.priority}`}\n >\n Priority: {item.priority}\n </span>\n </div>\n </header>\n\n {/* ── Metadata ─────────────────────────────────────────── */}\n <section\n className=\"feedback-admin-detail__metadata\"\n aria-label=\"Metadata\"\n >\n <dl className=\"feedback-admin-detail__metadata-grid\">\n {item.environment && (\n <>\n <dt>Environment</dt>\n <dd data-testid=\"detail-environment\">{item.environment}</dd>\n </>\n )}\n\n <dt>Submitted</dt>\n <dd data-testid=\"detail-submitted-at\">\n {formatDate(item.submittedAt)}\n </dd>\n\n <dt>Reporters</dt>\n <dd data-testid=\"detail-reporter-count\">{item.reporterCount}</dd>\n\n {item.assignedTo && (\n <>\n <dt>Assigned to</dt>\n <dd data-testid=\"detail-assigned-to\">{item.assignedTo}</dd>\n </>\n )}\n\n {item.version && (\n <>\n <dt>Version</dt>\n <dd data-testid=\"detail-version\">{item.version}</dd>\n </>\n )}\n\n {item.tags && item.tags.length > 0 && (\n <>\n <dt>Tags</dt>\n <dd data-testid=\"detail-tags\">{item.tags.join(', ')}</dd>\n </>\n )}\n </dl>\n </section>\n\n {/* ── Description / Content ────────────────────────────── */}\n <section\n className=\"feedback-admin-detail__description\"\n aria-label=\"Description\"\n >\n <h2 className=\"feedback-admin-detail__section-heading\">Description</h2>\n <p\n className=\"feedback-admin-detail__content\"\n data-testid=\"detail-content\"\n >\n {item.content}\n </p>\n </section>\n\n {/* ── Dialogue History ─────────────────────────────────── */}\n <section\n className=\"feedback-admin-detail__dialogue\"\n aria-label=\"Dialogue history\"\n >\n <h2 className=\"feedback-admin-detail__section-heading\">\n Dialogue history\n </h2>\n <DialogueHistory\n dialogue={item.dialogueHistory}\n className=\"feedback-admin-detail__dialogue-history\"\n />\n </section>\n\n {/* ── Screenshot ───────────────────────────────────────── */}\n <section\n className=\"feedback-admin-detail__screenshot\"\n aria-label=\"Screenshot\"\n >\n <h2 className=\"feedback-admin-detail__section-heading\">Screenshot</h2>\n <ScreenshotViewer\n {...(item.screenshotS3Key !== undefined\n ? { screenshotS3Key: item.screenshotS3Key }\n : {})}\n {...(generateScreenshotUrl !== undefined\n ? { generateScreenshotUrl }\n : {})}\n alt={`Screenshot for feedback ${item.id}`}\n className=\"feedback-admin-detail__screenshot-viewer\"\n />\n </section>\n\n {/* ── Browser Context ───────────────────────────────────── */}\n <section\n className=\"feedback-admin-detail__context\"\n aria-label=\"Browser context\"\n >\n <h2 className=\"feedback-admin-detail__section-heading\">\n Browser context\n </h2>\n <BrowserContext\n context={item.context}\n className=\"feedback-admin-detail__browser-context\"\n />\n </section>\n\n {/* ── Quick Actions ─────────────────────────────────────── */}\n <QuickActions\n item={item}\n updateItem={handleUpdateItem}\n onNavigate={onNavigate}\n {...(currentUser !== undefined ? { currentUser } : {})}\n className=\"feedback-admin-detail__quick-actions\"\n />\n </article>\n );\n}\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 * EditableFields — headless form-field components for FeedbackUpdatePage.\n *\n * Each component renders native HTML elements with correct label/htmlFor\n * associations and aria-* attributes. No external UI libraries.\n *\n * Exported as a namespace: EditableFields.StatusSelect, EditableFields.TypeSelect, etc.\n */\nimport React from 'react';\nimport type {\n FeedbackStatus,\n FeedbackType,\n FeedbackScope,\n FeedbackSize,\n} from '../../../core/types.js';\n\n// ─── Human-readable labels ────────────────────────────────────────\n\nconst STATUS_LABELS: Record<FeedbackStatus, string> = {\n open: 'Open',\n approved: 'Approved',\n deferred: 'Deferred',\n resolved: 'Resolved',\n wontfix: \"Won't fix\",\n};\n\nconst TYPE_LABELS: Record<FeedbackType, string> = {\n bug: 'Bug',\n feature: 'Feature',\n ux: 'UX issue',\n other: 'Other',\n};\n\nconst SCOPE_LABELS: Record<FeedbackScope, string> = {\n isolated: 'Isolated',\n 'cross-cutting': 'Cross-cutting',\n systemic: 'Systemic',\n};\n\nconst SIZE_LABELS: Record<FeedbackSize, string> = {\n trivial: 'Trivial',\n small: 'Small',\n medium: 'Medium',\n large: 'Large',\n};\n\nconst ALL_TYPES: FeedbackType[] = ['bug', 'feature', 'ux', 'other'];\nconst ALL_SCOPES: FeedbackScope[] = ['isolated', 'cross-cutting', 'systemic'];\nconst ALL_SIZES: FeedbackSize[] = ['trivial', 'small', 'medium', 'large'];\n\n// ─── StatusSelect ─────────────────────────────────────────────────\n\nexport interface StatusSelectProps {\n /** The current status (pre-selected) */\n currentStatus: FeedbackStatus;\n /** Valid target statuses (from useStatusTransition) */\n allowedTargets: FeedbackStatus[];\n /** Currently selected value */\n value: FeedbackStatus;\n /** Called when the user selects a different status */\n onChange: (status: FeedbackStatus) => void;\n /** Optional CSS class on the container */\n className?: string;\n}\n\n/**\n * StatusSelect — renders only the statuses present in `allowedTargets`.\n * The current status is shown as the pre-selected option but is NOT\n * included in the selectable options (to prevent self-transitions).\n */\nexport function StatusSelect({\n currentStatus,\n allowedTargets,\n value,\n onChange,\n className,\n}: StatusSelectProps): React.JSX.Element {\n const id = 'feedback-admin-status-select';\n return (\n <div className={className}>\n <label htmlFor={id}>Status</label>\n <select\n id={id}\n value={value}\n aria-label=\"Status\"\n onChange={(e) => onChange(e.target.value as FeedbackStatus)}\n >\n {/* Current status as pre-selected option */}\n <option value={currentStatus}>{STATUS_LABELS[currentStatus]}</option>\n {/* Allowed transition targets */}\n {allowedTargets.map((target) => (\n <option key={target} value={target}>\n {STATUS_LABELS[target]}\n </option>\n ))}\n </select>\n </div>\n );\n}\n\n// ─── TypeSelect ───────────────────────────────────────────────────\n\nexport interface TypeSelectProps {\n /** Currently selected type */\n value: FeedbackType;\n /** Called when the user selects a different type */\n onChange: (type: FeedbackType) => void;\n /** Optional CSS class on the container */\n className?: string;\n}\n\n/**\n * TypeSelect — renders all four FeedbackType values as options.\n */\nexport function TypeSelect({\n value,\n onChange,\n className,\n}: TypeSelectProps): React.JSX.Element {\n const id = 'feedback-admin-type-select';\n return (\n <div className={className}>\n <label htmlFor={id}>Type</label>\n <select\n id={id}\n value={value}\n aria-label=\"Type\"\n onChange={(e) => onChange(e.target.value as FeedbackType)}\n >\n {ALL_TYPES.map((type) => (\n <option key={type} value={type}>\n {TYPE_LABELS[type]}\n </option>\n ))}\n </select>\n </div>\n );\n}\n\n// ─── ScopeSelect ──────────────────────────────────────────────────\n\nexport interface ScopeSelectProps {\n /** Currently selected scope (undefined = not set) */\n value: FeedbackScope | undefined;\n /** Called when the user selects/clears scope */\n onChange: (scope: FeedbackScope | undefined) => void;\n /** Optional CSS class on the container */\n className?: string;\n}\n\n/**\n * ScopeSelect — renders a blank \"— not set —\" option plus all three FeedbackScope values.\n * Scope is optional, so the blank option allows clearing it.\n */\nexport function ScopeSelect({\n value,\n onChange,\n className,\n}: ScopeSelectProps): React.JSX.Element {\n const id = 'feedback-admin-scope-select';\n return (\n <div className={className}>\n <label htmlFor={id}>Scope</label>\n <select\n id={id}\n value={value ?? ''}\n aria-label=\"Scope\"\n onChange={(e) => {\n const v = e.target.value;\n onChange(v === '' ? undefined : (v as FeedbackScope));\n }}\n >\n <option value=\"\">— not set —</option>\n {ALL_SCOPES.map((scope) => (\n <option key={scope} value={scope}>\n {SCOPE_LABELS[scope]}\n </option>\n ))}\n </select>\n </div>\n );\n}\n\n// ─── SizeSelect ───────────────────────────────────────────────────\n\nexport interface SizeSelectProps {\n /** Currently selected size (undefined = not set) */\n value: FeedbackSize | undefined;\n /** Called when the user selects/clears size */\n onChange: (size: FeedbackSize | undefined) => void;\n /** Optional CSS class on the container */\n className?: string;\n}\n\n/**\n * SizeSelect — renders a blank \"— not set —\" option plus all four FeedbackSize values.\n * Size is optional, so the blank option allows clearing it.\n */\nexport function SizeSelect({\n value,\n onChange,\n className,\n}: SizeSelectProps): React.JSX.Element {\n const id = 'feedback-admin-size-select';\n return (\n <div className={className}>\n <label htmlFor={id}>Size</label>\n <select\n id={id}\n value={value ?? ''}\n aria-label=\"Size\"\n onChange={(e) => {\n const v = e.target.value;\n onChange(v === '' ? undefined : (v as FeedbackSize));\n }}\n >\n <option value=\"\">— not set —</option>\n {ALL_SIZES.map((size) => (\n <option key={size} value={size}>\n {SIZE_LABELS[size]}\n </option>\n ))}\n </select>\n </div>\n );\n}\n\n// ─── AssigneeInput ────────────────────────────────────────────────\n\nexport interface AssigneeInputProps {\n /** Current assignee (free text, or undefined) */\n value: string | undefined;\n /** Called when the assignee changes */\n onChange: (assignedTo: string | undefined) => void;\n /** Optional current user for \"Assign to me\" shortcut */\n currentUser?: { userId: string; name?: string };\n /** Optional CSS class on the container */\n className?: string;\n}\n\n/**\n * AssigneeInput — free-text input for assigning a feedback item.\n * When `currentUser` is provided, an \"Assign to me\" button pre-fills the field.\n */\nexport function AssigneeInput({\n value,\n onChange,\n currentUser,\n className,\n}: AssigneeInputProps): React.JSX.Element {\n const id = 'feedback-admin-assignee-input';\n return (\n <div className={className}>\n <label htmlFor={id}>Assigned to</label>\n <input\n id={id}\n type=\"text\"\n value={value ?? ''}\n aria-label=\"Assigned to\"\n onChange={(e) => {\n const v = e.target.value;\n onChange(v === '' ? undefined : v);\n }}\n />\n {currentUser !== undefined && (\n <button\n type=\"button\"\n onClick={() => onChange(currentUser.userId)}\n >\n Assign to me\n </button>\n )}\n </div>\n );\n}\n\n// ─── TagsInput ────────────────────────────────────────────────────\n\nexport interface TagsInputProps {\n /** Current tags as a string array */\n value: string[];\n /** Called with the parsed tag array when the input changes */\n onChange: (tags: string[]) => void;\n /** Optional CSS class on the container */\n className?: string;\n}\n\n/**\n * TagsInput — comma-separated text input that produces a string[] value.\n * Parses on each change: splits on commas, trims whitespace, removes empty strings.\n */\nexport function TagsInput({\n value,\n onChange,\n className,\n}: TagsInputProps): React.JSX.Element {\n const id = 'feedback-admin-tags-input';\n return (\n <div className={className}>\n <label htmlFor={id}>Tags</label>\n <input\n id={id}\n type=\"text\"\n value={value.join(', ')}\n aria-label=\"Tags\"\n placeholder=\"Comma-separated\"\n onChange={(e) => {\n const parsed = e.target.value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n onChange(parsed);\n }}\n />\n </div>\n );\n}\n\n// ─── CorrectionNote ───────────────────────────────────────────────\n\nexport interface CorrectionNoteProps {\n /** Current correction note text */\n value: string;\n /** Called when the text changes */\n onChange: (note: string) => void;\n /**\n * When true, the textarea is required — form save is blocked until\n * the note is non-empty (enforced by FeedbackUpdatePage).\n */\n required: boolean;\n /** Optional CSS class on the container */\n className?: string;\n}\n\n/**\n * CorrectionNote — textarea for explaining why a classification field changed.\n * Rendered only when a classification field (type, scope, size) has changed.\n * When `required` is true, sets aria-required=\"true\".\n */\nexport function CorrectionNote({\n value,\n onChange,\n required,\n className,\n}: CorrectionNoteProps): React.JSX.Element {\n const id = 'feedback-admin-correction-note';\n return (\n <div className={className}>\n <label htmlFor={id}>\n Correction note{required && <span aria-hidden=\"true\"> *</span>}\n </label>\n <textarea\n id={id}\n value={value}\n required={required}\n aria-required={required}\n aria-label=\"Correction note\"\n placeholder=\"Describe why the classification is changing…\"\n onChange={(e) => onChange(e.target.value)}\n />\n </div>\n );\n}\n\n// ─── Namespace export ─────────────────────────────────────────────\n\n/**\n * EditableFields — namespace of form-field components for FeedbackUpdatePage.\n *\n * Usage:\n * import { EditableFields } from './components/EditableFields.js';\n * <EditableFields.StatusSelect ... />\n */\nexport const EditableFields = {\n StatusSelect,\n TypeSelect,\n ScopeSelect,\n SizeSelect,\n AssigneeInput,\n TagsInput,\n CorrectionNote,\n} as const;\n","/**\n * FeedbackUpdatePage — headless component for editing mutable fields of a StoredFeedbackItem.\n *\n * Architecture: Headless/unstyled Inversion-of-Control component.\n * Consumer provides fetchItem, updateItem, onNavigate, currentUser, and className.\n * No direct DynamoDB or AWS SDK imports.\n */\nimport React, { useState, useEffect } from 'react';\nimport type {\n StoredFeedbackItem,\n FeedbackStatus,\n FeedbackType,\n FeedbackScope,\n FeedbackSize,\n CorrectionEntry,\n} from '../../core/types.js';\nimport { StatusBadge } from './components/StatusBadge.js';\nimport { TypeBadge } from './components/TypeBadge.js';\nimport { useStatusTransition } from './hooks/useStatusTransition.js';\nimport { EditableFields } from './components/EditableFields.js';\n\n// ─── Props ────────────────────────────────────────────────────────\n\nexport interface FeedbackUpdatePageProps {\n /** The ID of the feedback item to edit */\n feedbackId: string;\n /** Fetches the item by ID; result populates the form */\n fetchItem: (id: string) => Promise<StoredFeedbackItem>;\n /** Persists the changed fields; receives only the diff-patch */\n updateItem: (id: string, patch: Partial<StoredFeedbackItem>) => Promise<void>;\n /** Called to navigate (back to detail, cancel, after save) */\n onNavigate: (path: string) => void;\n /** Optional current user for \"Assign to me\" and correction tracking */\n currentUser?: { userId: string; name?: string };\n /** Optional CSS class on the root element */\n className?: string;\n}\n\n// ─── Component ────────────────────────────────────────────────────\n\n/**\n * FeedbackUpdatePage — edit the mutable fields of a feedback item.\n *\n * @example\n * <FeedbackUpdatePage\n * feedbackId=\"FB-2026-07-28-abc12345\"\n * fetchItem={api.getItem}\n * updateItem={api.updateItem}\n * onNavigate={navigate}\n * currentUser={{ userId: 'user-123' }}\n * />\n */\nexport function FeedbackUpdatePage({\n feedbackId,\n fetchItem,\n updateItem,\n onNavigate,\n currentUser,\n className,\n}: FeedbackUpdatePageProps): React.JSX.Element {\n // ─── Load state ────────────────────────────────────────────────\n const [item, setItem] = useState<StoredFeedbackItem | null>(null);\n const [loading, setLoading] = useState(true);\n\n // ─── Form state (mirrors mutable fields) ───────────────────────\n const [status, setStatus] = useState<FeedbackStatus>('open');\n const [type, setType] = useState<FeedbackType>('bug');\n const [scope, setScope] = useState<FeedbackScope | undefined>(undefined);\n const [size, setSize] = useState<FeedbackSize | undefined>(undefined);\n const [assignedTo, setAssignedTo] = useState<string | undefined>(undefined);\n const [tags, setTags] = useState<string[]>([]);\n const [correctionNote, setCorrectionNote] = useState('');\n\n // ─── Save state ────────────────────────────────────────────────\n const [saving, setSaving] = useState(false);\n\n // ─── Status transition hook ────────────────────────────────────\n const { allowedTargets } = useStatusTransition(status);\n\n // ─── Load item on mount ────────────────────────────────────────\n useEffect(() => {\n let cancelled = false;\n setLoading(true);\n fetchItem(feedbackId).then((loadedItem) => {\n if (cancelled) return;\n setItem(loadedItem);\n setStatus(loadedItem.status);\n setType(loadedItem.type);\n setScope(loadedItem.scope);\n setSize(loadedItem.size);\n setAssignedTo(loadedItem.assignedTo);\n setTags(loadedItem.tags ?? []);\n setCorrectionNote('');\n setLoading(false);\n });\n return () => { cancelled = true; };\n }, [feedbackId, fetchItem]);\n\n // ─── Derived state ─────────────────────────────────────────────\n\n /** True if any classification field has changed from the loaded item */\n const classificationChanged =\n item !== null && (\n type !== item.type ||\n scope !== item.scope ||\n size !== item.size\n );\n\n /** Save is disabled when: saving, note required but empty, or no item loaded */\n const saveDisabled =\n saving ||\n item === null ||\n (classificationChanged && correctionNote.trim() === '');\n\n // ─── Handlers ──────────────────────────────────────────────────\n\n function handleCancel(): void {\n onNavigate(`/feedback/${feedbackId}`);\n }\n\n async function handleSave(): Promise<void> {\n if (item === null || saveDisabled) return;\n\n setSaving(true);\n try {\n const now = new Date().toISOString();\n\n // Build diff-patch — only changed fields\n const patch: Partial<StoredFeedbackItem> = {\n lastModifiedAt: now,\n };\n\n if (status !== item.status) {\n patch.status = status;\n // Set resolved fields automatically on transition to 'resolved'\n if (status === 'resolved') {\n patch.resolvedAt = now;\n patch.resolvedBy = currentUser?.userId ?? 'unknown';\n }\n }\n\n if (type !== item.type) patch.type = type;\n if (scope !== item.scope) {\n if (scope !== undefined) {\n patch.scope = scope;\n } else {\n // Clear optional field by omitting it — use type assertion for exactOptionalPropertyTypes\n (patch as Record<string, unknown>).scope = undefined;\n }\n }\n if (size !== item.size) {\n if (size !== undefined) {\n patch.size = size;\n } else {\n (patch as Record<string, unknown>).size = undefined;\n }\n }\n if (assignedTo !== item.assignedTo) {\n if (assignedTo !== undefined) {\n patch.assignedTo = assignedTo;\n } else {\n (patch as Record<string, unknown>).assignedTo = undefined;\n }\n }\n if (JSON.stringify(tags) !== JSON.stringify(item.tags)) {\n patch.tags = tags;\n }\n\n // Append CorrectionEntry if any classification field changed\n if (classificationChanged) {\n const changedFields: string[] = [];\n const before: Record<string, unknown> = {};\n const after: Record<string, unknown> = {};\n\n if (type !== item.type) {\n changedFields.push('type');\n before.type = item.type;\n after.type = type;\n }\n if (scope !== item.scope) {\n changedFields.push('scope');\n before.scope = item.scope;\n after.scope = scope;\n }\n if (size !== item.size) {\n changedFields.push('size');\n before.size = item.size;\n after.size = size;\n }\n\n const correctionEntry: CorrectionEntry = {\n field: changedFields.join(', '),\n before,\n after,\n correctedBy: currentUser?.userId ?? 'unknown',\n correctedAt: now,\n reason: correctionNote,\n };\n\n patch.corrections = [...(item.corrections ?? []), correctionEntry];\n }\n\n await updateItem(feedbackId, patch);\n onNavigate(`/feedback/${feedbackId}`);\n } finally {\n setSaving(false);\n }\n }\n\n // ─── Render ────────────────────────────────────────────────────\n\n if (loading) {\n return (\n <article data-page=\"feedback-update\" className={className}>\n <p data-loading=\"true\">Loading…</p>\n </article>\n );\n }\n\n if (item === null) {\n return (\n <article data-page=\"feedback-update\" className={className}>\n <p>Feedback item not found.</p>\n </article>\n );\n }\n\n return (\n <article data-page=\"feedback-update\" className={className}>\n {/* ── Header ─────────────────────────────────────────────── */}\n <header>\n <h1>{item.id} — Edit</h1>\n <nav>\n <button type=\"button\" onClick={() => onNavigate(`/feedback/${feedbackId}`)}>\n ← Back to detail\n </button>\n <button type=\"button\" onClick={handleCancel}>\n Cancel\n </button>\n </nav>\n </header>\n\n {/* ── Read-only context ───────────────────────────────────── */}\n <section data-section=\"read-only-context\" aria-label=\"Read-only context\">\n <StatusBadge status={item.status} />\n <TypeBadge type={item.type} />\n\n <div data-field=\"content\">\n <p>{item.content}</p>\n </div>\n\n <div data-field=\"metadata\">\n <span data-field=\"submittedAt\">{item.submittedAt}</span>\n {item.environment && (\n <span data-field=\"environment\">{item.environment}</span>\n )}\n <span data-field=\"reporterCount\">{item.reporterCount}</span>\n </div>\n </section>\n\n {/* ── Edit form ───────────────────────────────────────────── */}\n <section data-section=\"edit-form\">\n <EditableFields.StatusSelect\n currentStatus={item.status}\n allowedTargets={allowedTargets}\n value={status}\n onChange={setStatus}\n />\n\n <EditableFields.TypeSelect\n value={type}\n onChange={setType}\n />\n\n <EditableFields.ScopeSelect\n value={scope}\n onChange={setScope}\n />\n\n <EditableFields.SizeSelect\n value={size}\n onChange={setSize}\n />\n\n <EditableFields.AssigneeInput\n value={assignedTo}\n onChange={setAssignedTo}\n {...(currentUser !== undefined ? { currentUser } : {})}\n />\n\n <EditableFields.TagsInput\n value={tags}\n onChange={setTags}\n />\n\n {classificationChanged && (\n <EditableFields.CorrectionNote\n value={correctionNote}\n onChange={setCorrectionNote}\n required={true}\n />\n )}\n </section>\n\n {/* ── Footer ─────────────────────────────────────────────── */}\n <footer>\n <button\n type=\"submit\"\n disabled={saveDisabled}\n aria-disabled={saveDisabled}\n onClick={() => { void handleSave(); }}\n >\n {saving ? 'Saving…' : 'Save changes'}\n </button>\n <button type=\"button\" onClick={handleCancel} disabled={saving}>\n Cancel\n </button>\n </footer>\n </article>\n );\n}\n"],"names":["STATUS_LABELS","TYPE_LABELS","ALLOWED_TRANSITIONS","formatDate"],"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 * 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":";;;AAcA,MAAM,iBAA0G;AAAA,EAC9G,MAAM;AAAA,EACN,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,SAAS;AACX;AAEA,MAAM,eAAsG;AAAA,EAC1G,KAAK;AAAA,EACL,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,OAAO;AACT;AAEA,SAAS,YAAY,EAAE,UAAsC;AAC3D,6BAAQ,OAAA,EAAM,SAAS,eAAe,MAAM,KAAK,aAAc,UAAA,QAAO;AACxE;AAEA,SAAS,UAAU,EAAE,QAAgC;AACnD,6BAAQ,OAAA,EAAM,SAAS,aAAa,IAAI,KAAK,aAAc,UAAA,MAAK;AAClE;AAEA,MAAM,mBAAmB,MAAM;AAAA,EAC7B,CAAC,EAAE,OAAO,UAAU,OAAO,YAAY,WAAW,mBAAmB,aAAa,GAAG,KAAA,GAAQ,QAAQ;AACnG,UAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,UAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,EAAE;AACzD,UAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,EAAE;AAErD,UAAM,gBAAgB,MAAM,QAAQ,MAAM;AACxC,UAAI,SAAS;AACb,UAAI,uBAAuB,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,YAAY;AACzE,UAAI,qBAAqB,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,UAAU;AACnE,UAAI,QAAQ;AACV,cAAM,IAAI,OAAO,YAAA;AACjB,iBAAS,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,YAAA,EAAc,SAAS,CAAC,KAAK,EAAE,GAAG,cAAc,SAAS,CAAC,CAAC;AAAA,MACtG;AACA,aAAO;AAAA,IACT,GAAG,CAAC,OAAO,cAAc,YAAY,MAAM,CAAC;AAE5C,UAAM,UAA8C,MAAM,QAAQ,MAAM;AAAA,MACtE,EAAE,QAAQ,WAAW,UAAU,CAAC,QAAQ,IAAI,SAAS,MAAM,GAAG,EAAE,KAAK,IAAI,IAAI,UAAU,KAAA;AAAA,MACvF,EAAE,QAAQ,QAAQ,UAAU,CAAC,QAAQ,oBAAC,WAAA,EAAU,MAAM,IAAI,KAAA,CAAM,GAAI,UAAU,KAAA;AAAA,MAC9E,EAAE,QAAQ,UAAU,UAAU,CAAC,QAAQ,oBAAC,aAAA,EAAY,QAAQ,IAAI,OAAA,CAAQ,GAAI,UAAU,KAAA;AAAA,MACtF,EAAE,QAAQ,YAAY,UAAU,YAAY,UAAU,KAAA;AAAA,MACtD,EAAE,QAAQ,aAAa,UAAU,iBAAiB,UAAU,KAAA;AAAA,MAC5D;AAAA,QACE,QAAQ;AAAA,QACR,UAAU,CAAC,QAAQ,IAAI,cAAc,IAAI,KAAK,IAAI,WAAW,EAAE,mBAAA,IAAuB;AAAA,QACtF,UAAU;AAAA,MAAA;AAAA,IACZ,GACC,EAAE;AAEL,UAAM,YAAyC,gBAAgB,aAAc;AAAA,MAC3E,SAAS;AAAA,QACP,GAAI,eAAe,CAAC,EAAE,OAAO,UAAU,OAAO,WAAW,YAAY,IAAI,OAAO,aAAA,CAAc,IAAI,CAAA;AAAA,QAClG,GAAI,aAAa,CAAC,EAAE,OAAO,QAAQ,OAAO,SAAS,UAAU,IAAI,OAAO,WAAA,CAAY,IAAI,CAAA;AAAA,MAAC;AAAA,MAE3F,gBAAgB,CAAC,UAAkB;AACjC,YAAI,UAAU,SAAU,iBAAgB,EAAE;AAC1C,YAAI,UAAU,OAAQ,eAAc,EAAE;AAAA,MACxC;AAAA,MACA,gBAAgB,MAAM;AAAE,wBAAgB,EAAE;AAAG,sBAAc,EAAE;AAAA,MAAG;AAAA,IAAA,IAC9D;AAEJ,UAAM,gBACJ,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,gBAAgB,EAAE,OAAO,KAAK;AAAA,UAC/C,WAAU;AAAA,UACV,cAAW;AAAA,UAEX,UAAA;AAAA,YAAA,oBAAC,UAAA,EAAO,OAAM,IAAG,UAAA,gBAAY;AAAA,YAC7B,oBAAC,UAAA,EAAO,OAAM,QAAO,UAAA,QAAI;AAAA,YACzB,oBAAC,UAAA,EAAO,OAAM,YAAW,UAAA,YAAQ;AAAA,YACjC,oBAAC,UAAA,EAAO,OAAM,YAAW,UAAA,YAAQ;AAAA,YACjC,oBAAC,UAAA,EAAO,OAAM,YAAW,UAAA,YAAQ;AAAA,YACjC,oBAAC,UAAA,EAAO,OAAM,WAAU,UAAA,YAAA,CAAS;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAEnC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,UAC7C,WAAU;AAAA,UACV,cAAW;AAAA,UAEX,UAAA;AAAA,YAAA,oBAAC,UAAA,EAAO,OAAM,IAAG,UAAA,aAAS;AAAA,YAC1B,oBAAC,UAAA,EAAO,OAAM,OAAM,UAAA,OAAG;AAAA,YACvB,oBAAC,UAAA,EAAO,OAAM,WAAU,UAAA,WAAO;AAAA,YAC/B,oBAAC,UAAA,EAAO,OAAM,MAAK,UAAA,MAAE;AAAA,YACrB,oBAAC,UAAA,EAAO,OAAM,SAAQ,UAAA,QAAA,CAAK;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAC7B,GACF;AAGF,UAAM,YAAY;AAAA,MAChB,OAAO;AAAA,MACP;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA,YAAY,CAAC,QAA4B,WAAW,aAAa,IAAI,EAAE,EAAE;AAAA,MACzE;AAAA,MACA,gBAAgB;AAAA,MAChB,mBAAmB;AAAA,MACnB;AAAA,MACA,QAAQ;AAAA,MACR,GAAI,YAAY,EAAE,UAAA,IAAc,CAAA;AAAA,MAChC,GAAI,oBAAoB,EAAE,YAAY,kBAAA,IAAsB,CAAA;AAAA,MAC5D,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,IAAC;AAGnC,WAAO,oBAAC,UAAA,EAA6B,KAAW,GAAG,UAAA,CAAW;AAAA,EAChE;AACF;AACA,iBAAiB,cAAc;ACnH/B,MAAM,qBAAqB,MAAM;AAAA,EAC/B,CAAC,EAAE,YAAY,WAAW,YAAY,aAAa,UAAA,GAAa,QAAQ;AACtE,UAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAoC,IAAI;AACtE,UAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,UAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAE5D,UAAM,UAAU,MAAM;AACpB,UAAI,YAAY;AAChB,iBAAW,IAAI;AACf,eAAS,IAAI;AACb,gBAAU,UAAU,EACjB,KAAK,CAAC,SAAS;AAAE,YAAI,CAAC,UAAW,SAAQ,IAAI;AAAA,MAAG,CAAC,EACjD,MAAM,CAAC,QAAQ;AAAE,YAAI,CAAC,UAAW,UAAS,KAAK,WAAW,qBAAqB;AAAA,MAAG,CAAC,EACnF,QAAQ,MAAM;AAAE,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAAG,CAAC;AACvD,aAAO,MAAM;AAAE,oBAAY;AAAA,MAAM;AAAA,IACnC,GAAG,CAAC,YAAY,SAAS,CAAC;AAE1B,QAAI,OAAO;AACT,aACE,oBAAC,OAAA,EAAI,KAAU,WACb,UAAA,oBAAC,YAAA,EAAW,OAAM,YAAW,eAAe,oBAAC,QAAA,EAAO,SAAQ,WAAU,SAAS,MAAM,WAAW,WAAW,GAAG,UAAA,eAAA,CAAY,GACxH,UAAA,oBAAC,OAAA,EAAI,WAAU,wBAAwB,UAAA,MAAA,CAAM,EAAA,CAC/C,GACF;AAAA,IAEJ;AAEA,UAAM,gBACJ,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAO,SAAQ,WAAU,SAAS,MAAM,WAAW,WAAW,GAAG,UAAA,eAAA,CAAY;AAAA,MAC9E,oBAAC,UAAO,SAAS,MAAM,WAAW,aAAa,UAAU,OAAO,GAAG,UAAA,OAAA,CAAI;AAAA,IAAA,GACzE;AAGF,WACE,oBAAC,OAAA,EAAI,KAAU,WACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,OAAO,MAAM,SAAS,MAAM,GAAG,EAAE,KAAK,YAAY,WAAW,MAAM,GAAG,EAAE,CAAC;AAAA,QACzE;AAAA,QACA;AAAA,QACA,QAAO;AAAA,QAEN,kBACC,qBAAA,UAAA,EACE,UAAA;AAAA,UAAA,qBAAC,eAAA,EAAc,OAAM,YACnB,UAAA;AAAA,YAAA,oBAAC,WAAA,EAAU,OAAM,UAAS,UAAA,oBAAC,eAAY,QAAQ,KAAK,QAAQ,EAAA,CAAE;AAAA,YAC9D,oBAAC,aAAU,OAAM,QAAO,8BAAC,WAAA,EAAU,MAAM,KAAK,KAAA,CAAM,EAAA,CAAE;AAAA,YACtD,oBAAC,WAAA,EAAU,OAAM,YAAY,eAAK,UAAS;AAAA,gCAC1C,WAAA,EAAU,OAAM,eAAe,UAAA,KAAK,eAAe,KAAI;AAAA,YACxD,oBAAC,WAAA,EAAU,OAAM,aAAa,UAAA,KAAK,cAAc,IAAI,KAAK,KAAK,WAAW,EAAE,eAAA,IAAmB,IAAA,CAAI;AAAA,gCAClG,WAAA,EAAU,OAAM,aAAa,UAAA,KAAK,iBAAiB,GAAE;AAAA,YACrD,KAAK,cAAc,oBAAC,aAAU,OAAM,eAAe,eAAK,YAAW;AAAA,YACnE,KAAK,SAAS,oBAAC,WAAA,EAAU,OAAM,SAAQ,UAAA,oBAAC,OAAA,EAAM,SAAQ,WAAW,UAAA,KAAK,MAAA,CAAM,GAAQ;AAAA,YACpF,KAAK,QAAQ,oBAAC,WAAA,EAAU,OAAM,QAAO,UAAA,oBAAC,OAAA,EAAM,SAAQ,WAAW,UAAA,KAAK,KAAA,CAAK,EAAA,CAAQ;AAAA,UAAA,GACpF;AAAA,UAEA,oBAAC,eAAA,EAAc,OAAM,WACnB,8BAAC,WAAA,EAAU,OAAM,eACf,UAAA,oBAAC,OAAE,WAAU,uBAAuB,UAAA,KAAK,QAAA,CAAQ,GACnD,GACF;AAAA,UAEC,KAAK,mBAAmB,KAAK,gBAAgB,SAAS,KACrD,oBAAC,eAAA,EAAc,OAAM,oBAAmB,aAAW,MAAC,aAAa,OAC9D,UAAA,KAAK,gBAAgB,IAAI,CAAC,OAAO,MAChC,qBAAC,WAAA,EAAkB,OAAO,MAAM,SAAS,SAAS,SAAS,aACzD,UAAA;AAAA,YAAA,oBAAC,KAAA,EAAE,WAAU,+BAA+B,UAAA,MAAM,SAAQ;AAAA,YACzD,MAAM,aACL,oBAAC,QAAA,EAAK,WAAU,iCACb,UAAA,IAAI,KAAK,MAAM,SAAS,EAAE,eAAA,EAAe,CAC5C;AAAA,UAAA,KALY,CAOhB,CACD,GACH;AAAA,UAGD,KAAK,WACJ,qBAAC,eAAA,EAAc,OAAM,mBAAkB,aAAW,MAAC,aAAa,OAC7D,UAAA;AAAA,YAAA,KAAK,QAAQ,OAAO,oBAAC,WAAA,EAAU,OAAM,OAAO,UAAA,KAAK,QAAQ,IAAA,CAAI;AAAA,YAC7D,KAAK,QAAQ,aAAa,oBAAC,aAAU,OAAM,cAAc,UAAA,KAAK,QAAQ,UAAA,CAAU;AAAA,YAChF,KAAK,QAAQ,YAAY,oBAAC,WAAA,EAAU,OAAM,YAAY,UAAA,GAAG,KAAK,QAAQ,SAAS,KAAK,IAAI,KAAK,QAAQ,SAAS,MAAM,GAAA,CAAG;AAAA,UAAA,GAC1H;AAAA,UAGD,KAAK,QAAQ,KAAK,KAAK,SAAS,KAC/B,oBAAC,eAAA,EAAc,OAAM,QACnB,UAAA,oBAAC,WAAA,EAAU,OAAM,QACf,UAAA,oBAAC,OAAA,EAAI,WAAU,wBACZ,UAAA,KAAK,KAAK,IAAI,CAAC,QAAQ,oBAAC,OAAA,EAAgB,SAAQ,WAAW,UAAA,IAAA,GAAxB,GAA4B,CAAQ,EAAA,CAC1E,GACF,EAAA,CACF;AAAA,QAAA,EAAA,CAEJ;AAAA,MAAA;AAAA,IAAA,GAGN;AAAA,EAEJ;AACF;AACA,mBAAmB,cAAc;ACpGjC,MAAM,sBAAgE;AAAA,EACpE,MAAU,CAAC,YAAY,YAAY,SAAS;AAAA,EAC5C,UAAU,CAAC,QAAQ,YAAY,UAAU;AAAA,EACzC,UAAU,CAAC,QAAQ,YAAY,SAAS;AAAA,EACxC,UAAU,CAAC,QAAQ,UAAU;AAAA,EAC7B,SAAU,CAAC,QAAQ,UAAU;AAC/B;AAiBO,SAAS,oBAAoB,eAA0D;AAC5F,QAAM,iBAAiB,oBAAoB,aAAa,KAAK,CAAA;AAC7D,SAAO,EAAE,eAAA;AACX;AC3BA,MAAM,iBAAiB;AAAA,EACrB,EAAE,OAAO,QAAQ,OAAO,OAAA;AAAA,EACxB,EAAE,OAAO,YAAY,OAAO,WAAA;AAAA,EAC5B,EAAE,OAAO,YAAY,OAAO,WAAA;AAAA,EAC5B,EAAE,OAAO,YAAY,OAAO,WAAA;AAAA,EAC5B,EAAE,OAAO,WAAW,OAAO,YAAA;AAC7B;AAEA,MAAM,eAAe;AAAA,EACnB,EAAE,OAAO,OAAO,OAAO,MAAA;AAAA,EACvB,EAAE,OAAO,WAAW,OAAO,UAAA;AAAA,EAC3B,EAAE,OAAO,MAAM,OAAO,WAAA;AAAA,EACtB,EAAE,OAAO,SAAS,OAAO,QAAA;AAC3B;AAEA,MAAM,gBAAgB;AAAA,EACpB,EAAE,OAAO,IAAI,OAAO,cAAA;AAAA,EACpB,EAAE,OAAO,aAAa,OAAO,YAAA;AAAA,EAC7B,EAAE,OAAO,QAAQ,OAAO,OAAA;AAAA,EACxB,EAAE,OAAO,YAAY,OAAO,WAAA;AAC9B;AAEA,MAAM,eAAe;AAAA,EACnB,EAAE,OAAO,IAAI,OAAO,cAAA;AAAA,EACpB,EAAE,OAAO,WAAW,OAAO,UAAA;AAAA,EAC3B,EAAE,OAAO,SAAS,OAAO,QAAA;AAAA,EACzB,EAAE,OAAO,UAAU,OAAO,SAAA;AAAA,EAC1B,EAAE,OAAO,SAAS,OAAO,QAAA;AAC3B;AAEA,MAAM,qBAAqB,MAAM;AAAA,EAC/B,CAAC,EAAE,YAAY,WAAW,YAAY,YAAY,aAAa,UAAA,GAAa,QAAQ;AAClF,UAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAoC,IAAI;AACtE,UAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,UAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAGhD,UAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAyB,MAAM;AACjE,UAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAuB,KAAK;AAC1D,UAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAiB,EAAE;AACnD,UAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAiB,EAAE;AACjD,UAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,EAAE;AACrD,UAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,EAAE;AACzC,UAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,EAAE;AAG7D,UAAM,UAAU,MAAM;AACpB,UAAI,YAAY;AAChB,iBAAW,IAAI;AACf,gBAAU,UAAU,EACjB,KAAK,CAAC,SAAS;AACd,YAAI,UAAW;AACf,gBAAQ,IAAI;AACZ,kBAAU,KAAK,MAAM;AACrB,gBAAQ,KAAK,IAAI;AACjB,iBAAS,KAAK,SAAS,EAAE;AACzB,gBAAQ,KAAK,QAAQ,EAAE;AACvB,sBAAc,KAAK,cAAc,EAAE;AACnC,gBAAQ,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE;AAAA,MACrC,CAAC,EACA,QAAQ,MAAM;AAAE,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAAG,CAAC;AACvD,aAAO,MAAM;AAAE,oBAAY;AAAA,MAAM;AAAA,IACnC,GAAG,CAAC,YAAY,SAAS,CAAC;AAE1B,UAAM,EAAE,eAAA,IAAmB,oBAAoB,MAAM;AACrD,UAAM,gBAAgB,eAAe;AAAA,MACnC,CAAC,QAAQ,IAAI,UAAU,UAAU,eAAe,SAAS,IAAI,KAAuB;AAAA,IAAA;AAItF,UAAM,wBAAwB,SAAS,SAAS,KAAK,QAAQ,WAAW,KAAK,SAAS,OAAO,UAAU,KAAK,QAAQ;AACpH,UAAM,UAAU,SACd,WAAW,KAAK,UAChB,SAAS,KAAK,QACd,WAAW,KAAK,SAAS,OACzB,UAAU,KAAK,QAAQ,OACvB,gBAAgB,KAAK,cAAc,OACnC,UAAU,KAAK,MAAM,KAAK,IAAI,KAAK;AAGrC,UAAM,aAAa,YAAY;AAC7B,UAAI,CAAC,KAAM;AACX,gBAAU,IAAI;AACd,UAAI;AACF,cAAM,QAAqC,EAAE,qCAAoB,KAAA,GAAO,cAAY;AACpF,YAAI,WAAW,KAAK,OAAQ,OAAM,SAAS;AAC3C,YAAI,SAAS,KAAK,KAAM,OAAM,OAAO;AACrC,YAAI,WAAW,KAAK,SAAS,IAAM,OAAkC,QAAQ,SAAS;AACtF,YAAI,UAAU,KAAK,QAAQ,IAAM,OAAkC,OAAO,QAAQ;AAClF,YAAI,gBAAgB,KAAK,cAAc,IAAM,OAAkC,aAAa,cAAc;AAC1G,cAAM,UAAU,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAA,CAAM,EAAE,OAAO,OAAO;AACnE,YAAI,KAAK,UAAU,OAAO,MAAM,KAAK,UAAU,KAAK,QAAQ,CAAA,CAAE,EAAG,OAAM,OAAO;AAE9E,YAAI,WAAW,cAAc,KAAK,WAAW,YAAY;AACvD,gBAAM,cAAa,oBAAI,KAAA,GAAO,YAAA;AAC9B,gBAAM,aAAa,aAAa,UAAU;AAAA,QAC5C;AAEA,YAAI,yBAAyB,gBAAgB;AAC3C,gBAAM,cAAc;AAAA,YAClB,GAAI,KAAK,eAAe,CAAA;AAAA,YACxB;AAAA,cACE,OAAO,CAAC,SAAS,KAAK,QAAQ,QAAQ,WAAW,KAAK,SAAS,OAAO,SAAS,UAAU,KAAK,QAAQ,OAAO,MAAM,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAAA,cAC9I,QAAQ,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO,MAAM,KAAK,KAAA;AAAA,cACzD,OAAO,EAAE,MAAM,OAAO,SAAS,QAAW,MAAM,QAAQ,OAAA;AAAA,cACxD,aAAa,aAAa,UAAU;AAAA,cACpC,cAAa,oBAAI,KAAA,GAAO,YAAA;AAAA,cACxB,QAAQ;AAAA,YAAA;AAAA,UACV;AAAA,QAEJ;AAEA,cAAM,WAAW,YAAY,KAAK;AAClC,mBAAW,aAAa,UAAU,EAAE;AAAA,MACtC,UAAA;AACE,kBAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAEA,UAAM,gBACJ,oBAAC,QAAA,EAAO,SAAQ,WAAU,SAAS,MAAM,WAAW,aAAa,UAAU,EAAE,GAAG,UAAA,UAEhF;AAGF,WACE,oBAAC,OAAA,EAAI,KAAU,WACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,OAAO,SAAS,MAAM,SAAS,MAAM,GAAG,EAAE,KAAK,WAAW,MAAM,GAAG,EAAE,CAAC;AAAA,QACtE;AAAA,QACA,aAAa;AAAA,QACb,SACE,qBAAC,YAAA,EAAW,OAAO,CAAC,CAAC,SAAS,QAC5B,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAO,SAAQ,WAAU,SAAS,MAAM,WAAW,aAAa,UAAU,EAAE,GAAG,UAAA,SAAA,CAAM;AAAA,UACtF;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAS;AAAA,cACT,UAAU,UAAU,CAAC,WAAY,CAAC,CAAC,yBAAyB,CAAC;AAAA,cAE5D,mBAAS,YAAY;AAAA,YAAA;AAAA,UAAA;AAAA,QACxB,GACF;AAAA,QAEF,QAAO;AAAA,QAEN,UAAA,QACC,qBAAC,OAAA,EAAI,WAAU,aAEb,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,mDACb,UAAA;AAAA,YAAA,oBAAC,KAAA,EAAE,WAAU,sCAAqC,UAAA,uBAAmB;AAAA,YACrE,oBAAC,KAAA,EAAE,WAAU,+BAA+B,eAAK,SAAQ;AAAA,YACzD,qBAAC,OAAA,EAAI,WAAU,iDACb,UAAA;AAAA,cAAA,oBAAC,QAAA,EAAM,UAAA,KAAK,cAAc,IAAI,KAAK,KAAK,WAAW,EAAE,eAAA,IAAmB,GAAA,CAAG;AAAA,cAC3E,oBAAC,QAAA,EAAM,UAAA,KAAK,YAAA,CAAY;AAAA,mCACvB,QAAA,EAAM,UAAA;AAAA,gBAAA,KAAK,iBAAiB;AAAA,gBAAE;AAAA,cAAA,EAAA,CAAY;AAAA,YAAA,EAAA,CAC7C;AAAA,UAAA,GACF;AAAA,UAGA,qBAAC,OAAA,EAAI,WAAU,yCACb,UAAA;AAAA,YAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAM;AAAA,gBACN,SAAS;AAAA,gBACT,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,UAAU,CAAmB;AAAA,cAAA;AAAA,YAAA;AAAA,YAEhD;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAM;AAAA,gBACN,SAAS;AAAA,gBACT,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,QAAQ,CAAiB;AAAA,cAAA;AAAA,YAAA;AAAA,YAE5C;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAM;AAAA,gBACN,SAAS;AAAA,gBACT,OAAO;AAAA,gBACP,UAAU;AAAA,cAAA;AAAA,YAAA;AAAA,YAEZ;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAM;AAAA,gBACN,SAAS;AAAA,gBACT,OAAO;AAAA,gBACP,UAAU;AAAA,cAAA;AAAA,YAAA;AAAA,YAEZ;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAM;AAAA,gBACN,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,gBAC7C,aAAa,aAAa,QAAQ,aAAa,UAAU;AAAA,gBACxD,GAAI,cAAc,EAAE,aAAa,QAAQ,YAAY,QAAQ,YAAY,MAAM,OAAO,CAAA;AAAA,cAAC;AAAA,YAAA;AAAA,YAE1F;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAM;AAAA,gBACN,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,gBACvC,aAAY;AAAA,gBACZ,aAAY;AAAA,cAAA;AAAA,YAAA;AAAA,UACd,GACF;AAAA,UAGC,yBACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAM;AAAA,cACN,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,kBAAkB,EAAE,OAAO,KAAK;AAAA,cACjD,UAAQ;AAAA,cACP,GAAI,CAAC,iBAAiB,EAAE,OAAO,8CAAA,IAAkD,CAAA;AAAA,cAClF,aAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAKf,KAAK,QAAQ,KAAK,KAAK,SAAS,KAC/B,oBAAC,OAAA,EAAI,WAAU,wBACZ,UAAA,KAAK,KAAK,IAAI,CAAC,QAAQ,oBAAC,OAAA,EAAgB,SAAQ,WAAW,UAAA,OAAxB,GAA4B,CAAQ,EAAA,CAC1E;AAAA,QAAA,EAAA,CAEJ;AAAA,MAAA;AAAA,IAAA,GAGN;AAAA,EAEJ;AACF;AACA,mBAAmB,cAAc;"}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { StoredFeedbackItem
|
|
1
|
+
import { StoredFeedbackItem } from '../../core/types.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Base props shared by all admin page components.
|
|
5
|
+
* Follows Inversion of Control — consumer provides navigation and user context.
|
|
6
|
+
*/
|
|
3
7
|
export interface AdminPageProps {
|
|
4
8
|
className?: string;
|
|
5
9
|
currentUser?: {
|
|
@@ -8,31 +12,33 @@ export interface AdminPageProps {
|
|
|
8
12
|
};
|
|
9
13
|
onNavigate: (path: string) => void;
|
|
10
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Props for the FeedbackListPage component.
|
|
17
|
+
* Consumer provides items (already fetched); component handles filtering/sorting via
|
|
18
|
+
* anton-bakker-tailwind-components ListPage template.
|
|
19
|
+
*/
|
|
11
20
|
export interface FeedbackListPageProps extends AdminPageProps {
|
|
12
21
|
items: StoredFeedbackItem[];
|
|
13
22
|
loading?: boolean;
|
|
14
23
|
emptyStateMessage?: string;
|
|
15
|
-
pageSize?: number;
|
|
16
|
-
tableClassName?: string;
|
|
17
|
-
filterClassName?: string;
|
|
18
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Props for the FeedbackDetailPage component.
|
|
27
|
+
* Consumer provides fetchItem; component handles loading/display via
|
|
28
|
+
* anton-bakker-tailwind-components DetailPage template.
|
|
29
|
+
*/
|
|
19
30
|
export interface FeedbackDetailPageProps extends AdminPageProps {
|
|
20
31
|
feedbackId: string;
|
|
21
32
|
fetchItem: (id: string) => Promise<StoredFeedbackItem>;
|
|
22
|
-
generateScreenshotUrl?: (s3Key: string) => Promise<string>;
|
|
23
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Props for the FeedbackUpdatePage component.
|
|
36
|
+
* Consumer provides fetchItem + updateItem; component handles form state via
|
|
37
|
+
* anton-bakker-tailwind-components UpdatePage template.
|
|
38
|
+
*/
|
|
24
39
|
export interface FeedbackUpdatePageProps extends AdminPageProps {
|
|
25
40
|
feedbackId: string;
|
|
26
41
|
fetchItem: (id: string) => Promise<StoredFeedbackItem>;
|
|
27
42
|
updateItem: (id: string, patch: Partial<StoredFeedbackItem>) => Promise<void>;
|
|
28
43
|
}
|
|
29
|
-
export interface AdminFilterState {
|
|
30
|
-
status: FeedbackStatus | '';
|
|
31
|
-
type: FeedbackType | '';
|
|
32
|
-
environment: string;
|
|
33
|
-
}
|
|
34
|
-
export interface AdminSortState {
|
|
35
|
-
field: 'priority' | 'title' | 'type' | 'status' | 'date' | 'reporterCount' | 'submittedAt';
|
|
36
|
-
direction: 'asc' | 'desc';
|
|
37
|
-
}
|
|
38
44
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/react/admin/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,
|
|
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;CACxD;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/dist/react.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
3
|
-
import { v as validateConfig,
|
|
2
|
+
import React__default, { createContext, lazy, useMemo, useReducer, useEffect, useCallback, Suspense, useContext, useState, useRef } from "react";
|
|
3
|
+
import { v as validateConfig, r as resolveConfig, d as detectLocale, t } from "./chunks/i18n-Du-or3T4.js";
|
|
4
|
+
import { c } from "./chunks/submitResolver-DsfkrUHY.js";
|
|
4
5
|
const FeedbackContext = createContext(null);
|
|
5
6
|
FeedbackContext.displayName = "FeedbackContext";
|
|
6
7
|
const INITIAL_PANEL_STATE = { status: "idle" };
|
|
@@ -313,7 +314,7 @@ function FeedbackProvider({
|
|
|
313
314
|
};
|
|
314
315
|
}, [resolvedConfig, internalState, locale, open, close]);
|
|
315
316
|
if (!contextValue) {
|
|
316
|
-
return
|
|
317
|
+
return React__default.createElement(React__default.Fragment, null, children);
|
|
317
318
|
}
|
|
318
319
|
return /* @__PURE__ */ jsxs(FeedbackContext.Provider, { value: contextValue, children: [
|
|
319
320
|
children,
|
|
@@ -405,7 +406,7 @@ function injectStyles() {
|
|
|
405
406
|
function FeedbackButton({ className, label }) {
|
|
406
407
|
const ctx = useContext(FeedbackContext);
|
|
407
408
|
const { state, open } = useFeedback();
|
|
408
|
-
|
|
409
|
+
React__default.useEffect(() => {
|
|
409
410
|
injectStyles();
|
|
410
411
|
}, []);
|
|
411
412
|
if (state.status !== "idle") return null;
|
|
@@ -1165,10 +1166,10 @@ function Portal({ children }) {
|
|
|
1165
1166
|
setIsClient(true);
|
|
1166
1167
|
}, []);
|
|
1167
1168
|
if (!isClient || typeof document === "undefined") {
|
|
1168
|
-
return
|
|
1169
|
+
return React__default.createElement(React__default.Fragment, null, children);
|
|
1169
1170
|
}
|
|
1170
|
-
return
|
|
1171
|
-
|
|
1171
|
+
return React__default.createElement(
|
|
1172
|
+
React__default.Fragment,
|
|
1172
1173
|
null,
|
|
1173
1174
|
children
|
|
1174
1175
|
);
|
|
@@ -1435,134 +1436,6 @@ const FeedbackPanel$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.def
|
|
|
1435
1436
|
__proto__: null,
|
|
1436
1437
|
FeedbackPanel
|
|
1437
1438
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
1438
|
-
const VALID_TYPES = ["bug", "feature", "ux", "other"];
|
|
1439
|
-
function validatePayload(body) {
|
|
1440
|
-
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
1441
|
-
return { valid: false, errors: ["Request body must be a JSON object"] };
|
|
1442
|
-
}
|
|
1443
|
-
const obj = body;
|
|
1444
|
-
const errors = [];
|
|
1445
|
-
if (!obj["title"] || typeof obj["title"] !== "string" || obj["title"].trim().length === 0) {
|
|
1446
|
-
errors.push("title is required and must be a non-empty string");
|
|
1447
|
-
} else if (obj["title"].trim().length < 1) {
|
|
1448
|
-
errors.push("title must be at least 1 character");
|
|
1449
|
-
}
|
|
1450
|
-
if (!obj["type"]) {
|
|
1451
|
-
errors.push("type is required");
|
|
1452
|
-
} else if (!VALID_TYPES.includes(obj["type"])) {
|
|
1453
|
-
errors.push(`type must be one of: ${VALID_TYPES.join(", ")} (got: ${String(obj["type"])})`);
|
|
1454
|
-
}
|
|
1455
|
-
if (obj["description"] !== void 0 && typeof obj["description"] !== "string") {
|
|
1456
|
-
errors.push("description must be a string if provided");
|
|
1457
|
-
}
|
|
1458
|
-
if (errors.length > 0) {
|
|
1459
|
-
return { valid: false, errors };
|
|
1460
|
-
}
|
|
1461
|
-
return {
|
|
1462
|
-
valid: true,
|
|
1463
|
-
payload: {
|
|
1464
|
-
title: obj["title"].trim(),
|
|
1465
|
-
...obj["description"] !== void 0 && { description: obj["description"] },
|
|
1466
|
-
type: obj["type"],
|
|
1467
|
-
...obj["scope"] !== void 0 && { scope: obj["scope"] },
|
|
1468
|
-
...obj["size"] !== void 0 && { size: obj["size"] },
|
|
1469
|
-
...obj["screenshots"] !== void 0 && { screenshots: obj["screenshots"] },
|
|
1470
|
-
...obj["consoleErrors"] !== void 0 && { consoleErrors: obj["consoleErrors"] },
|
|
1471
|
-
...obj["systemInfo"] !== void 0 && { systemInfo: obj["systemInfo"] }
|
|
1472
|
-
}
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
async function storeFeedback(payload, config, feedbackId) {
|
|
1476
|
-
if (!config.tableName || !config.awsRegion) {
|
|
1477
|
-
return;
|
|
1478
|
-
}
|
|
1479
|
-
const dynamoPrefix = ["@aws", "-sdk/client-dynamo", "db"].join("");
|
|
1480
|
-
const { DynamoDBClient, PutItemCommand } = await import(
|
|
1481
|
-
/* @vite-ignore */
|
|
1482
|
-
dynamoPrefix
|
|
1483
|
-
);
|
|
1484
|
-
const client = new DynamoDBClient({ region: config.awsRegion });
|
|
1485
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1486
|
-
await client.send(
|
|
1487
|
-
new PutItemCommand({
|
|
1488
|
-
TableName: config.tableName,
|
|
1489
|
-
Item: {
|
|
1490
|
-
PK: { S: `PROJECT#${config.appName}` },
|
|
1491
|
-
SK: { S: `FB#${now}#${feedbackId}` },
|
|
1492
|
-
id: { S: feedbackId },
|
|
1493
|
-
appName: { S: config.appName },
|
|
1494
|
-
type: { S: payload.type },
|
|
1495
|
-
status: { S: "open" },
|
|
1496
|
-
content: { S: payload.title },
|
|
1497
|
-
submittedAt: { S: now },
|
|
1498
|
-
lastModifiedAt: { S: now },
|
|
1499
|
-
environment: { S: config.environment ?? "production" }
|
|
1500
|
-
}
|
|
1501
|
-
})
|
|
1502
|
-
);
|
|
1503
|
-
}
|
|
1504
|
-
function generateId() {
|
|
1505
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1506
|
-
const uuid8 = crypto.randomUUID().replace(/-/g, "").slice(0, 8);
|
|
1507
|
-
return `FB-${date}-${uuid8}`;
|
|
1508
|
-
}
|
|
1509
|
-
function createIntakeResolver(config) {
|
|
1510
|
-
return async (req, res) => {
|
|
1511
|
-
const contentType = (req.headers?.["content-type"] ?? "").toString();
|
|
1512
|
-
if (!contentType.includes("application/json")) {
|
|
1513
|
-
res.status(400).json({
|
|
1514
|
-
errors: ["Content-Type must be application/json"]
|
|
1515
|
-
});
|
|
1516
|
-
return;
|
|
1517
|
-
}
|
|
1518
|
-
const validation = validatePayload(req.body);
|
|
1519
|
-
if (!validation.valid) {
|
|
1520
|
-
res.status(400).json({ errors: validation.errors });
|
|
1521
|
-
return;
|
|
1522
|
-
}
|
|
1523
|
-
const { payload } = validation;
|
|
1524
|
-
const feedbackId = generateId();
|
|
1525
|
-
try {
|
|
1526
|
-
await withSpan(
|
|
1527
|
-
"feedback.submit",
|
|
1528
|
-
{
|
|
1529
|
-
app: config.appName,
|
|
1530
|
-
environment: config.environment ?? "production"
|
|
1531
|
-
},
|
|
1532
|
-
async (span) => {
|
|
1533
|
-
try {
|
|
1534
|
-
await storeFeedback(payload, config, feedbackId);
|
|
1535
|
-
if (span) {
|
|
1536
|
-
span.setAttribute?.("feedbackId", feedbackId);
|
|
1537
|
-
}
|
|
1538
|
-
res.status(201).json({
|
|
1539
|
-
id: feedbackId,
|
|
1540
|
-
status: "open"
|
|
1541
|
-
});
|
|
1542
|
-
} catch (storageError) {
|
|
1543
|
-
const isDynamoUnavailable = storageError instanceof Error && (storageError.name === "ServiceUnavailableException" || storageError.name === "ProvisionedThroughputExceededException" || storageError.message.includes("ECONNREFUSED") || storageError.message.includes("unavailable") || storageError.message.includes("DynamoDB"));
|
|
1544
|
-
if (isDynamoUnavailable) {
|
|
1545
|
-
res.status(503).json({
|
|
1546
|
-
retryable: true,
|
|
1547
|
-
error: "Storage service temporarily unavailable. Please try again."
|
|
1548
|
-
});
|
|
1549
|
-
} else {
|
|
1550
|
-
res.status(503).json({
|
|
1551
|
-
retryable: true,
|
|
1552
|
-
error: "Failed to store feedback. Please try again."
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
);
|
|
1558
|
-
} catch {
|
|
1559
|
-
res.status(503).json({
|
|
1560
|
-
retryable: true,
|
|
1561
|
-
error: "Internal error. Please try again."
|
|
1562
|
-
});
|
|
1563
|
-
}
|
|
1564
|
-
};
|
|
1565
|
-
}
|
|
1566
1439
|
export {
|
|
1567
1440
|
FeedbackButton,
|
|
1568
1441
|
FeedbackConfirm,
|
|
@@ -1570,7 +1443,7 @@ export {
|
|
|
1570
1443
|
FeedbackPanel,
|
|
1571
1444
|
FeedbackProvider,
|
|
1572
1445
|
FeedbackRecorder,
|
|
1573
|
-
createIntakeResolver,
|
|
1446
|
+
c as createIntakeResolver,
|
|
1574
1447
|
useFeedback
|
|
1575
1448
|
};
|
|
1576
1449
|
//# sourceMappingURL=react.js.map
|