diff --git a/src/app/(authenticated)/forms/[id]/page.tsx b/src/app/(authenticated)/forms/[id]/page.tsx index 35f04a17..7fce11b3 100644 --- a/src/app/(authenticated)/forms/[id]/page.tsx +++ b/src/app/(authenticated)/forms/[id]/page.tsx @@ -12,6 +12,7 @@ import { DashboardShell } from "@/components/ui/dashboard-shell" import { FormDescription } from "@/components/forms/form-description" import { FormsSubPageShell, FormsPanel } from "@/components/forms/forms-sub-page-shell" import { CreateManualResponseButtonWrapper } from "@/components/forms/create-manual-response-button-wrapper" +import { FormResponsesExportDialog } from "@/components/forms/form-responses-export-dialog" import { canAccessForm, canEditForm } from "@/lib/access-control" export const metadata = { @@ -80,7 +81,7 @@ export default async function FormPage({ params }: FormPageProps) {
{/* Gestão (somente quem pode editar) — à esquerda */} {canEdit && ( -
+
+ {form.spreadsheetExportEnabled && ( + + )}
)} diff --git a/src/components/forms/central-view.tsx b/src/components/forms/central-view.tsx index 33be77bd..f826d210 100644 --- a/src/components/forms/central-view.tsx +++ b/src/components/forms/central-view.tsx @@ -28,6 +28,7 @@ import { Zap, } from "lucide-react" import { useToast } from "@/hooks/use-toast" +import { FormResponsesExportDialog } from "@/components/forms/form-responses-export-dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -122,27 +123,49 @@ export function CentralView() { } }, [responseIdParam]) + const [selectedFormId, setSelectedFormId] = React.useState(formIdParam ?? undefined) + const [selectedAttendantId, setSelectedAttendantId] = React.useState(undefined) + + const { data: managedForms = [] } = api.form.listForKanbanFilter.useQuery() + const { data: activeFormData } = api.form.getById.useQuery( + selectedFormId ?? "", + { enabled: !!selectedFormId }, + ) + + React.useEffect(() => { + if (formIdParam) { + setSelectedFormId(formIdParam) + } else if (managedForms.length === 1 && managedForms[0]?.id) { + setSelectedFormId(managedForms[0].id) + } + }, [formIdParam, managedForms]) + + const selectedForm = React.useMemo(() => { + if (!selectedFormId) return null + if (activeFormData) return activeFormData + return managedForms.find((f) => f.id === selectedFormId) ?? null + }, [selectedFormId, activeFormData, managedForms]) + const { toast } = useToast() const utils = api.useUtils() const { data: kpisData } = api.formResponse.getQueueKpis.useQuery({ tagIds: selectedTagIds.length > 0 ? selectedTagIds : undefined, - formIds: formIdParam ? [formIdParam] : undefined, + formIds: selectedFormId ? [selectedFormId] : undefined, search: query.trim() || undefined, hasResponse: hasResponseFilter, }) const infiniteQueue = api.formResponse.listQueueInfinite.useInfiniteQuery( { - limit: 25, + limit: 50, status: tab !== "ALL" ? tab : undefined, tagIds: selectedTagIds.length > 0 ? selectedTagIds : undefined, - formIds: formIdParam ? [formIdParam] : undefined, + formIds: selectedFormId ? [selectedFormId] : undefined, search: query.trim() || undefined, hasResponse: hasResponseFilter, }, { getNextPageParam: (lastPage) => lastPage.nextCursor, - enabled: view === "fila", }, ) @@ -152,14 +175,38 @@ export function CentralView() { return (infiniteQueue.data?.pages.flatMap((page) => page.items) ?? []) as unknown as FormResponse[] }, [infiniteQueue.data]) - const responses = queueResponses const isLoading = infiniteQueue.isLoading function handleSelect(id: string) { setSelectedId((prev) => (prev === id ? null : id)) } - const filtered = queueResponses + const availableAttendants = React.useMemo(() => { + const map = new Map() + for (const r of queueResponses) { + if (r.assignedTo?.userId && r.assignedTo?.name) { + map.set(r.assignedTo.userId, { + id: r.assignedTo.userId, + name: r.assignedTo.name, + }) + } + } + return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name)) + }, [queueResponses]) + + const hasUnassigned = React.useMemo(() => { + return queueResponses.some((r) => !r.assignedTo?.userId) + }, [queueResponses]) + + const filtered = React.useMemo(() => { + if (!selectedAttendantId || selectedAttendantId === "all") { + return queueResponses + } + if (selectedAttendantId === "unassigned") { + return queueResponses.filter((r) => !r.assignedTo?.userId) + } + return queueResponses.filter((r) => r.assignedTo?.userId === selectedAttendantId) + }, [queueResponses, selectedAttendantId]) // Caso o chamado selecionado pela URL não esteja na 1ª página da fila const cleanSelectedNum = selectedId ? selectedId.replace(/^#/, "") : "" @@ -175,18 +222,42 @@ export function CentralView() { const currentResponse = React.useMemo(() => { if (!selectedId) return null return ( - responses.find((r) => r.id === selectedId || (r.number != null && String(r.number) === cleanSelectedNum)) ?? + filtered.find((r) => r.id === selectedId || (r.number != null && String(r.number) === cleanSelectedNum)) ?? (fallbackResponse ? (fallbackResponse as unknown as FormResponse) : null) ) - }, [responses, selectedId, fallbackResponse, cleanSelectedNum]) - - const counts = kpisData ?? { - notStarted: 0, - inProgress: 0, - done: 0, - recentDone: 0, - aging: 0, - } + }, [filtered, selectedId, fallbackResponse, cleanSelectedNum]) + + const counts = React.useMemo(() => { + if (selectedAttendantId && selectedAttendantId !== "all") { + const todayStart = new Date() + todayStart.setHours(0, 0, 0, 0) + const yesterday24h = new Date(Date.now() - 24 * 60 * 60 * 1000) + + const notStarted = filtered.filter((r) => r.status === "NOT_STARTED").length + const inProgress = filtered.filter((r) => r.status === "IN_PROGRESS").length + const done = filtered.filter((r) => r.status === "COMPLETED").length + const recentDone = filtered.filter( + (r) => r.status === "COMPLETED" && new Date(r.updatedAt) >= todayStart, + ).length + const aging = filtered.filter( + (r) => + (r.status === "NOT_STARTED" || r.status === "IN_PROGRESS") && + new Date(r.createdAt) <= yesterday24h, + ).length + + return { notStarted, inProgress, done, recentDone, aging } + } + + return ( + kpisData ?? { + notStarted: 0, + inProgress: 0, + done: 0, + recentDone: 0, + aging: 0, + } + ) + }, [selectedAttendantId, filtered, kpisData]) const updateStatus = api.formResponse.updateStatus.useMutation({ onSuccess: () => { @@ -303,6 +374,13 @@ export function CentralView() {

+ {selectedForm && selectedForm.spreadsheetExportEnabled && ( + + )}
-
-
- +
+
+ setQuery(e.target.value)} - className="pl-9 pr-8 h-9 text-xs bg-background w-full" + className="pl-8 pr-7 h-8 text-xs bg-background w-full rounded-lg" /> {query && ( )}
+ {managedForms.length > 1 && ( + + )} + + +