Refina métricas de tempo e filtros de alertas
This commit is contained in:
parent
ef25cbe799
commit
b880aa3ea6
3 changed files with 36 additions and 22 deletions
|
|
@ -17,10 +17,12 @@ export function AdminAlertsManager() {
|
||||||
const [range, setRange] = useState<string>("30d")
|
const [range, setRange] = useState<string>("30d")
|
||||||
const { session, convexUserId } = useAuth()
|
const { session, convexUserId } = useAuth()
|
||||||
const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID
|
const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID
|
||||||
const now = new Date()
|
const { start, end } = useMemo(() => {
|
||||||
const days = range === "7d" ? 7 : range === "30d" ? 30 : range === "90d" ? 90 : null
|
const daysLocal = range === "7d" ? 7 : range === "30d" ? 30 : range === "90d" ? 90 : null
|
||||||
const end = now.getTime()
|
const endLocal = Date.now()
|
||||||
const start = days ? end - days * 24 * 60 * 60 * 1000 : undefined
|
const startLocal = daysLocal ? endLocal - daysLocal * 24 * 60 * 60 * 1000 : undefined
|
||||||
|
return { start: startLocal, end: endLocal }
|
||||||
|
}, [range])
|
||||||
|
|
||||||
const alertsRaw = useQuery(
|
const alertsRaw = useQuery(
|
||||||
api.alerts.list,
|
api.alerts.list,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
import { format, formatDistanceToNow } from "date-fns"
|
import { format, formatDistanceToNow } from "date-fns"
|
||||||
import { ptBR } from "date-fns/locale"
|
import { ptBR } from "date-fns/locale"
|
||||||
import { IconClock, IconFileTypePdf, IconPlayerPause, IconPlayerPlay } from "@tabler/icons-react"
|
import { IconClock, IconDownload, IconInfoCircle, IconPlayerPause, IconPlayerPlay } from "@tabler/icons-react"
|
||||||
import { useMutation, useQuery } from "convex/react"
|
import { useMutation, useQuery } from "convex/react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { api } from "@/convex/_generated/api"
|
import { api } from "@/convex/_generated/api"
|
||||||
|
|
@ -30,6 +30,7 @@ import {
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
||||||
|
|
||||||
interface TicketHeaderProps {
|
interface TicketHeaderProps {
|
||||||
ticket: TicketWithDetails
|
ticket: TicketWithDetails
|
||||||
|
|
@ -447,19 +448,6 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
return (
|
return (
|
||||||
<div className={cardClass}>
|
<div className={cardClass}>
|
||||||
<div className="absolute right-6 top-6 flex items-center gap-3">
|
<div className="absolute right-6 top-6 flex items-center gap-3">
|
||||||
{workSummary ? (
|
|
||||||
<>
|
|
||||||
<Badge className="inline-flex h-9 items-center gap-2 rounded-full border border-slate-200 bg-white px-3 text-sm font-semibold text-neutral-700">
|
|
||||||
<IconClock className="size-4 text-neutral-700" /> Interno: {formatDuration(internalWorkedMs)}
|
|
||||||
</Badge>
|
|
||||||
<Badge className="inline-flex h-9 items-center gap-2 rounded-full border border-slate-200 bg-white px-3 text-sm font-semibold text-neutral-700">
|
|
||||||
<IconClock className="size-4 text-neutral-700" /> Externo: {formatDuration(externalWorkedMs)}
|
|
||||||
</Badge>
|
|
||||||
<Badge className="inline-flex h-9 items-center gap-2 rounded-full border border-slate-200 bg-white px-3 text-sm font-semibold text-neutral-700">
|
|
||||||
<IconClock className="size-4 text-neutral-700" /> Total: {formattedTotalWorked}
|
|
||||||
</Badge>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
{!editing ? (
|
{!editing ? (
|
||||||
<Button size="sm" className={editButtonClass} onClick={() => setEditing(true)}>
|
<Button size="sm" className={editButtonClass} onClick={() => setEditing(true)}>
|
||||||
Editar
|
Editar
|
||||||
|
|
@ -474,8 +462,32 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
disabled={exportingPdf}
|
disabled={exportingPdf}
|
||||||
title="Exportar PDF"
|
title="Exportar PDF"
|
||||||
>
|
>
|
||||||
{exportingPdf ? <Spinner className="size-4 text-neutral-700" /> : <IconFileTypePdf className="size-5" />}
|
{exportingPdf ? <Spinner className="size-4 text-neutral-700" /> : <IconDownload className="size-5" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
{workSummary ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge className="inline-flex h-9 items-center gap-2 rounded-full border border-slate-200 bg-white px-3 text-sm font-semibold text-neutral-700">
|
||||||
|
<IconClock className="size-4 text-neutral-700" /> Tempo total: {formattedTotalWorked}
|
||||||
|
</Badge>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 bg-white text-neutral-600 transition hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#00d6eb]/30"
|
||||||
|
aria-label="Ver detalhamento das horas internas e externas"
|
||||||
|
>
|
||||||
|
<IconInfoCircle className="size-4" />
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className="rounded-lg border border-slate-200 bg-white px-3 py-2 text-xs font-medium text-neutral-700 shadow-lg">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span>Horas internas: {formatDuration(internalWorkedMs)}</span>
|
||||||
|
<span>Horas externas: {formatDuration(externalWorkedMs)}</span>
|
||||||
|
</div>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<DeleteTicketDialog ticketId={ticket.id as Id<"tickets">} />
|
<DeleteTicketDialog ticketId={ticket.id as Id<"tickets">} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||||
|
|
|
||||||
|
|
@ -145,12 +145,12 @@ export function TicketsTable({ tickets = ticketsMock }: TicketsTableProps) {
|
||||||
<TableHead className="hidden w-[120px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 lg:table-cell">
|
<TableHead className="hidden w-[120px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 lg:table-cell">
|
||||||
Fila
|
Fila
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="hidden w-[180px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 lg:table-cell">
|
|
||||||
Empresa
|
|
||||||
</TableHead>
|
|
||||||
<TableHead className="hidden w-[80px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 md:table-cell">
|
<TableHead className="hidden w-[80px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 md:table-cell">
|
||||||
Canal
|
Canal
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead className="hidden w-[180px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 lg:table-cell">
|
||||||
|
Empresa
|
||||||
|
</TableHead>
|
||||||
<TableHead className="hidden w-[100px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 md:table-cell">
|
<TableHead className="hidden w-[100px] pl-1 pr-3 py-3 text-left text-[11px] font-semibold uppercase tracking-wide text-neutral-600 first:pl-6 last:pr-6 md:table-cell">
|
||||||
Prioridade
|
Prioridade
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue