feat: agenda polish, SLA sync, filters
This commit is contained in:
parent
7fb6c65d9a
commit
6ab8a6ce89
40 changed files with 2771 additions and 154 deletions
121
src/app/agenda/agenda-page-client.tsx
Normal file
121
src/app/agenda/agenda-page-client.tsx
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useQuery } from "convex/react"
|
||||
import { format } from "date-fns"
|
||||
import { ptBR } from "date-fns/locale/pt-BR"
|
||||
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import type { Id } from "@/convex/_generated/dataModel"
|
||||
import { DEFAULT_TENANT_ID } from "@/lib/constants"
|
||||
import { mapTicketsFromServerList } from "@/lib/mappers/ticket"
|
||||
import type { Ticket } from "@/lib/schemas/ticket"
|
||||
import { AppShell } from "@/components/app-shell"
|
||||
import { SiteHeader } from "@/components/site-header"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { CalendarPlus } from "lucide-react"
|
||||
import { useAuth } from "@/lib/auth-client"
|
||||
import { AgendaFilters, AgendaFilterState, AgendaPeriod, defaultAgendaFilters } from "@/components/agenda/agenda-filters"
|
||||
import { AgendaSummaryView } from "@/components/agenda/agenda-summary-view"
|
||||
import { AgendaCalendarView } from "@/components/agenda/agenda-calendar-view"
|
||||
import { buildAgendaDataset, type AgendaDataset } from "@/lib/agenda-utils"
|
||||
|
||||
export function AgendaPageClient() {
|
||||
const [activeTab, setActiveTab] = useState<"summary" | "calendar">("summary")
|
||||
const [filters, setFilters] = useState<AgendaFilterState>(defaultAgendaFilters)
|
||||
|
||||
const { convexUserId, session } = useAuth()
|
||||
const userId = convexUserId as Id<"users"> | null
|
||||
const tenantId = session?.user?.tenantId ?? DEFAULT_TENANT_ID
|
||||
|
||||
const ticketsArgs = userId
|
||||
? {
|
||||
tenantId,
|
||||
viewerId: userId,
|
||||
status: undefined,
|
||||
priority: filters.priorities.length ? filters.priorities : undefined,
|
||||
queueId: undefined,
|
||||
channel: undefined,
|
||||
assigneeId: filters.onlyMyTickets ? userId : undefined,
|
||||
search: undefined,
|
||||
}
|
||||
: "skip"
|
||||
|
||||
const ticketsRaw = useQuery(api.tickets.list, ticketsArgs)
|
||||
const mappedTickets = useMemo<Ticket[] | null>(() => {
|
||||
if (!Array.isArray(ticketsRaw)) return null
|
||||
return mapTicketsFromServerList(ticketsRaw as unknown[])
|
||||
}, [ticketsRaw])
|
||||
|
||||
const [cachedTickets, setCachedTickets] = useState<Ticket[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (mappedTickets) {
|
||||
setCachedTickets(mappedTickets)
|
||||
}
|
||||
}, [mappedTickets])
|
||||
|
||||
const effectiveTickets = mappedTickets ?? cachedTickets
|
||||
const isInitialLoading = !mappedTickets && cachedTickets.length === 0 && ticketsArgs !== "skip"
|
||||
|
||||
const dataset: AgendaDataset = useMemo(
|
||||
() => buildAgendaDataset(effectiveTickets, filters),
|
||||
[effectiveTickets, filters]
|
||||
)
|
||||
|
||||
const greeting = getGreetingMessage()
|
||||
const firstName = session?.user?.name?.split(" ")[0] ?? session?.user?.email?.split("@")[0] ?? "equipe"
|
||||
const rangeDescription = formatRangeDescription(filters.period, dataset.range)
|
||||
const headerLead = `${greeting}, ${firstName}! ${rangeDescription}`
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
header={
|
||||
<SiteHeader
|
||||
title="Agenda"
|
||||
lead={headerLead}
|
||||
secondaryAction={
|
||||
<Button variant="secondary" size="sm" className="gap-2" disabled>
|
||||
<CalendarPlus className="size-4" />
|
||||
Em breve: novo compromisso
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col gap-6 px-4 lg:px-6">
|
||||
<AgendaFilters filters={filters} onChange={setFilters} queues={dataset.availableQueues} />
|
||||
<Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as typeof activeTab)}>
|
||||
<TabsList className="mb-4">
|
||||
<TabsTrigger value="summary">Resumo</TabsTrigger>
|
||||
<TabsTrigger value="calendar">Calendário</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="summary" className="focus-visible:outline-none">
|
||||
<AgendaSummaryView data={dataset} isLoading={isInitialLoading} />
|
||||
</TabsContent>
|
||||
<TabsContent value="calendar" className="focus-visible:outline-none">
|
||||
<AgendaCalendarView events={dataset.calendarEvents} range={dataset.range} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</AppShell>
|
||||
)
|
||||
}
|
||||
|
||||
function getGreetingMessage(date: Date = new Date()) {
|
||||
const hour = date.getHours()
|
||||
if (hour < 12) return "Bom dia"
|
||||
if (hour < 18) return "Boa tarde"
|
||||
return "Boa noite"
|
||||
}
|
||||
|
||||
function formatRangeDescription(period: AgendaPeriod, range: { start: Date; end: Date }) {
|
||||
if (period === "today") {
|
||||
return `Hoje é ${format(range.start, "eeee, d 'de' MMMM", { locale: ptBR })}`
|
||||
}
|
||||
if (period === "week") {
|
||||
return `Semana de ${format(range.start, "d MMM", { locale: ptBR })} a ${format(range.end, "d MMM", { locale: ptBR })}`
|
||||
}
|
||||
return `Mês de ${format(range.start, "MMMM 'de' yyyy", { locale: ptBR })}`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue