import { useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { FeedbackDetailSheet } from "./components/feedback-detail-sheet"; import { FeedbackEmptyState } from "./components/feedback-empty-state"; import { FeedbackRequestRow } from "./components/feedback-request-row"; import { FeedbackTopBar } from "./components/feedback-top-bar"; import { IdeationAToolbar } from "./components/ideation-a-toolbar"; import { MOCK_CURRENT_USERS, MOCK_FEEDBACK } from "./mock-data"; import type { FeedbackCurrentUser, FeedbackItem, FeedbackRole } from "./types"; export interface FeedbackIdeationAProps { items?: FeedbackItem[]; title?: string; /** Fires when the toolbar's "New request" button is pressed (open to every role). */ onNewRequest?: () => void; /** Viewer role — drives the toolbar (filters + New request) and detail actions. */ role?: FeedbackRole; /** Signed-in user — scopes the "My requests" filter. Defaults to the role's mock user. */ currentUser?: FeedbackCurrentUser; /** * Render the built-in page top bar. Default `true` (standalone use). Pass * `false` when a host app supplies its own top bar — the board then fills its * parent (`h-full`) instead of the viewport (`h-screen`). */ showTopBar?: boolean; /** Fires when the "Load more" button is pressed (host fetches the next page). */ onLoadMore?: () => void; /** Whether more items can be loaded — shows the "Load more" button. */ hasMore?: boolean; /** Whether a load-more fetch is in flight — disables the button. */ isLoadingMore?: boolean; /** * Controlled status filter. When provided together with `onStatusChange`, * the component skips client-side filtering — the host is responsible for * passing pre-displayed `items` (e.g. from a server call). */ status?: string; onStatusChange?: (value: string) => void; /** Controlled type filter (same controlled semantics as `status`). */ type?: string; onTypeChange?: (value: string) => void; /** Controlled search query (same controlled semantics as `status`). */ query?: string; onQueryChange?: (value: string) => void; /** * Fired when admin confirms approval (priority chosen). May return a promise — * the detail drawer stays open until it resolves and closes on success only. */ onApprove?: (item: FeedbackItem, priority: string) => void | Promise; /** * Fired when admin clicks Decline. May return a promise — the detail drawer * stays open until it resolves and closes on success only. */ onDecline?: (item: FeedbackItem) => void | Promise; /** * Fired when `onApprove` or `onDecline` rejects. The detail drawer stays open; * the host decides how to surface the failure. */ onError?: (error: unknown) => void; /** Fired when the upvote button is toggled on a row. */ onUpvote?: (item: FeedbackItem) => void; } /** * Ideation A — an upvote board. The toolbar (tabs + search + New request) sits on * one line above a list of full-width rows: upvote control on the left, * title/tags/details in the middle, a preview of the related screen on the right. */ export function FeedbackIdeationA({ items = MOCK_FEEDBACK, title = "Product Ideas & Feature Requests", onNewRequest, role = "staff", currentUser = MOCK_CURRENT_USERS[role], showTopBar = true, onLoadMore, hasMore = false, isLoadingMore = false, status: statusProp, onStatusChange: onStatusChangeProp, type: typeProp, onTypeChange: onTypeChangeProp, query: queryProp, onQueryChange: onQueryChangeProp, onApprove, onDecline, onError, onUpvote, }: FeedbackIdeationAProps) { const isControlled = onStatusChangeProp !== undefined; const [localStatus, setLocalStatus] = useState("all"); const [localType, setLocalType] = useState("all"); const [localQuery, setLocalQuery] = useState(""); const status = isControlled ? (statusProp ?? "all") : localStatus; const setStatus = isControlled ? onStatusChangeProp : setLocalStatus; const typeFilter = isControlled ? (typeProp ?? "all") : localType; const setTypeFilter = isControlled ? onTypeChangeProp! : setLocalType; const query = isControlled ? (queryProp ?? "") : localQuery; const setQuery = isControlled ? onQueryChangeProp! : setLocalQuery; const [selected, setSelected] = useState(null); const [sheetOpen, setSheetOpen] = useState(false); function openDetail(item: FeedbackItem) { setSelected(item); setSheetOpen(true); } const displayed = useMemo(() => { if (isControlled) { return [...items].sort( (a, b) => Number(b.type === "bug") - Number(a.type === "bug"), ); } const q = query.trim().toLowerCase(); return items .filter((item) => { const matchesFilter = status === "all" ? true : status === "my-requests" ? item.reporter.name === currentUser.name : item.status === status; const matchesType = typeFilter === "all" || item.type === typeFilter; const matchesQuery = q === "" || item.title.toLowerCase().includes(q) || item.problemStatement.toLowerCase().includes(q) || item.area.toLowerCase().includes(q); return matchesFilter && matchesType && matchesQuery; }) .sort((a, b) => Number(b.type === "bug") - Number(a.type === "bug")); }, [items, status, typeFilter, query, currentUser.name, isControlled]); return (
{showTopBar && }
{displayed.length === 0 ? ( ) : (
{displayed.map((item) => ( ))}
)} {/* Host-driven pagination — only shown when more pages remain. */} {hasMore && (
)}
); }