Align report filters and update work session flows
This commit is contained in:
parent
17c1de2272
commit
ff9d95746e
7 changed files with 106 additions and 83 deletions
|
|
@ -2806,7 +2806,8 @@ export const changeAssignee = mutation({
|
||||||
throw new ConvexError("Gestores não podem reatribuir chamados")
|
throw new ConvexError("Gestores não podem reatribuir chamados")
|
||||||
}
|
}
|
||||||
const normalizedStatus = normalizeStatus(ticketDoc.status)
|
const normalizedStatus = normalizeStatus(ticketDoc.status)
|
||||||
if (normalizedStatus === "AWAITING_ATTENDANCE" || ticketDoc.activeSessionId) {
|
const hasActiveSession = Boolean(ticketDoc.activeSessionId)
|
||||||
|
if (normalizedStatus === "AWAITING_ATTENDANCE" && !hasActiveSession) {
|
||||||
throw new ConvexError("Pause o atendimento antes de reatribuir o chamado")
|
throw new ConvexError("Pause o atendimento antes de reatribuir o chamado")
|
||||||
}
|
}
|
||||||
const currentAssigneeId = ticketDoc.assigneeId ?? null
|
const currentAssigneeId = ticketDoc.assigneeId ?? null
|
||||||
|
|
@ -2833,7 +2834,51 @@ export const changeAssignee = mutation({
|
||||||
avatarUrl: assignee.avatarUrl ?? undefined,
|
avatarUrl: assignee.avatarUrl ?? undefined,
|
||||||
teams: assignee.teams ?? undefined,
|
teams: assignee.teams ?? undefined,
|
||||||
}
|
}
|
||||||
await ctx.db.patch(ticketId, { assigneeId, assigneeSnapshot, updatedAt: now });
|
const ticketPatch: Partial<Doc<"tickets">> = {
|
||||||
|
assigneeId,
|
||||||
|
assigneeSnapshot,
|
||||||
|
updatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasActiveSession) {
|
||||||
|
const session = await ctx.db.get(ticketDoc.activeSessionId as Id<"ticketWorkSessions">)
|
||||||
|
if (session) {
|
||||||
|
const durationMs = Math.max(0, now - session.startedAt)
|
||||||
|
const sessionType = (session.workType ?? "INTERNAL").toUpperCase()
|
||||||
|
const deltaInternal = sessionType === "INTERNAL" ? durationMs : 0
|
||||||
|
const deltaExternal = sessionType === "EXTERNAL" ? durationMs : 0
|
||||||
|
|
||||||
|
await ctx.db.patch(session._id, {
|
||||||
|
stoppedAt: now,
|
||||||
|
durationMs,
|
||||||
|
})
|
||||||
|
|
||||||
|
ticketPatch.totalWorkedMs = (ticketDoc.totalWorkedMs ?? 0) + durationMs
|
||||||
|
ticketPatch.internalWorkedMs = (ticketDoc.internalWorkedMs ?? 0) + deltaInternal
|
||||||
|
ticketPatch.externalWorkedMs = (ticketDoc.externalWorkedMs ?? 0) + deltaExternal
|
||||||
|
|
||||||
|
const newSessionId = await ctx.db.insert("ticketWorkSessions", {
|
||||||
|
ticketId,
|
||||||
|
agentId: assigneeId,
|
||||||
|
workType: sessionType,
|
||||||
|
startedAt: now,
|
||||||
|
})
|
||||||
|
|
||||||
|
ticketPatch.activeSessionId = newSessionId
|
||||||
|
ticketPatch.working = true
|
||||||
|
ticketPatch.status = "AWAITING_ATTENDANCE"
|
||||||
|
|
||||||
|
ticketDoc.totalWorkedMs = ticketPatch.totalWorkedMs as number
|
||||||
|
ticketDoc.internalWorkedMs = ticketPatch.internalWorkedMs as number
|
||||||
|
ticketDoc.externalWorkedMs = ticketPatch.externalWorkedMs as number
|
||||||
|
ticketDoc.activeSessionId = newSessionId
|
||||||
|
} else {
|
||||||
|
ticketPatch.activeSessionId = undefined
|
||||||
|
ticketPatch.working = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.db.patch(ticketId, ticketPatch);
|
||||||
await ctx.db.insert("ticketEvents", {
|
await ctx.db.insert("ticketEvents", {
|
||||||
ticketId,
|
ticketId,
|
||||||
type: "ASSIGNEE_CHANGED",
|
type: "ASSIGNEE_CHANGED",
|
||||||
|
|
@ -3717,12 +3762,11 @@ export const startWork = mutation({
|
||||||
}
|
}
|
||||||
const ticketDoc = ticket as Doc<"tickets">
|
const ticketDoc = ticket as Doc<"tickets">
|
||||||
const viewer = await requireTicketStaff(ctx, actorId, ticketDoc)
|
const viewer = await requireTicketStaff(ctx, actorId, ticketDoc)
|
||||||
const isAdmin = viewer.role === "ADMIN"
|
|
||||||
const currentAssigneeId = ticketDoc.assigneeId ?? null
|
const currentAssigneeId = ticketDoc.assigneeId ?? null
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
if (currentAssigneeId && currentAssigneeId !== actorId && !isAdmin) {
|
if (!currentAssigneeId) {
|
||||||
throw new ConvexError("Somente o responsável atual pode iniciar este chamado")
|
throw new ConvexError("Defina um responsável antes de iniciar o atendimento")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ticketDoc.activeSessionId) {
|
if (ticketDoc.activeSessionId) {
|
||||||
|
|
@ -3735,26 +3779,9 @@ export const startWork = mutation({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let assigneePatched = false
|
|
||||||
const previousAssigneeIdForStart = currentAssigneeId
|
|
||||||
const previousAssigneeNameForStart =
|
|
||||||
((ticketDoc.assigneeSnapshot as { name?: string } | null)?.name as string | undefined) ?? "Não atribuído"
|
|
||||||
|
|
||||||
if (!currentAssigneeId) {
|
|
||||||
const assigneeSnapshot = {
|
|
||||||
name: viewer.user.name,
|
|
||||||
email: viewer.user.email,
|
|
||||||
avatarUrl: viewer.user.avatarUrl ?? undefined,
|
|
||||||
teams: viewer.user.teams ?? undefined,
|
|
||||||
}
|
|
||||||
await ctx.db.patch(ticketId, { assigneeId: actorId, assigneeSnapshot, updatedAt: now })
|
|
||||||
ticketDoc.assigneeId = actorId
|
|
||||||
assigneePatched = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionId = await ctx.db.insert("ticketWorkSessions", {
|
const sessionId = await ctx.db.insert("ticketWorkSessions", {
|
||||||
ticketId,
|
ticketId,
|
||||||
agentId: actorId,
|
agentId: currentAssigneeId,
|
||||||
workType: (workType ?? "INTERNAL").toUpperCase(),
|
workType: (workType ?? "INTERNAL").toUpperCase(),
|
||||||
startedAt: now,
|
startedAt: now,
|
||||||
})
|
})
|
||||||
|
|
@ -3768,23 +3795,6 @@ export const startWork = mutation({
|
||||||
...slaStartPatch,
|
...slaStartPatch,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (assigneePatched) {
|
|
||||||
await ctx.db.insert("ticketEvents", {
|
|
||||||
ticketId,
|
|
||||||
type: "ASSIGNEE_CHANGED",
|
|
||||||
payload: {
|
|
||||||
assigneeId: actorId,
|
|
||||||
assigneeName: viewer.user.name,
|
|
||||||
actorId,
|
|
||||||
actorName: viewer.user.name,
|
|
||||||
actorAvatar: viewer.user.avatarUrl ?? undefined,
|
|
||||||
previousAssigneeId: previousAssigneeIdForStart,
|
|
||||||
previousAssigneeName: previousAssigneeNameForStart,
|
|
||||||
},
|
|
||||||
createdAt: now,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.db.insert("ticketEvents", {
|
await ctx.db.insert("ticketEvents", {
|
||||||
ticketId,
|
ticketId,
|
||||||
type: "WORK_STARTED",
|
type: "WORK_STARTED",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ type DateRangeButtonProps = {
|
||||||
onChange: (next: DateRangeValue) => void
|
onChange: (next: DateRangeValue) => void
|
||||||
className?: string
|
className?: string
|
||||||
clearLabel?: string
|
clearLabel?: string
|
||||||
|
align?: "left" | "center"
|
||||||
}
|
}
|
||||||
|
|
||||||
function strToDate(value?: string | null): Date | undefined {
|
function strToDate(value?: string | null): Date | undefined {
|
||||||
|
|
@ -41,7 +42,14 @@ function formatPtBR(value?: Date): string {
|
||||||
return value ? value.toLocaleDateString("pt-BR") : ""
|
return value ? value.toLocaleDateString("pt-BR") : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DateRangeButton({ from, to, onChange, className, clearLabel = "Limpar período" }: DateRangeButtonProps) {
|
export function DateRangeButton({
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
onChange,
|
||||||
|
className,
|
||||||
|
clearLabel = "Limpar período",
|
||||||
|
align = "left",
|
||||||
|
}: DateRangeButtonProps) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const range: DateRange | undefined = useMemo(
|
const range: DateRange | undefined = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|
@ -123,7 +131,7 @@ export function DateRangeButton({ from, to, onChange, className, clearLabel = "L
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={`flex h-10 w-full items-center justify-start gap-2 rounded-2xl border-slate-300 bg-white/95 text-sm font-semibold text-neutral-700 ${className ?? ""}`}
|
className={`flex h-10 w-full items-center gap-2 rounded-2xl border-slate-300 bg-white/95 text-sm font-semibold text-neutral-700 ${align === "center" ? "justify-center text-center" : "justify-start text-left"} ${className ?? ""}`}
|
||||||
>
|
>
|
||||||
<IconCalendar className="size-4 text-neutral-500" />
|
<IconCalendar className="size-4 text-neutral-500" />
|
||||||
<span className="truncate">{label}</span>
|
<span className="truncate">{label}</span>
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,8 @@ export function HoursReport() {
|
||||||
onValueChange={(value) => setCompanyId(value ?? "all")}
|
onValueChange={(value) => setCompanyId(value ?? "all")}
|
||||||
options={companyOptions}
|
options={companyOptions}
|
||||||
placeholder="Todas as empresas"
|
placeholder="Todas as empresas"
|
||||||
triggerClassName="h-10 w-full rounded-2xl border border-border/60 bg-white px-3 text-left text-sm font-semibold text-neutral-800 lg:w-64"
|
triggerClassName="h-10 w-full rounded-2xl border border-border/60 bg-white px-3 text-sm font-semibold text-neutral-800 lg:w-64"
|
||||||
|
align="center"
|
||||||
/>
|
/>
|
||||||
<DateRangeButton
|
<DateRangeButton
|
||||||
from={dateFrom}
|
from={dateFrom}
|
||||||
|
|
@ -246,23 +247,33 @@ export function HoursReport() {
|
||||||
setDateFrom(from)
|
setDateFrom(from)
|
||||||
setDateTo(to)
|
setDateTo(to)
|
||||||
}}
|
}}
|
||||||
className="w-full min-w-[200px] lg:w-auto"
|
className="w-full min-w-[200px] lg:w-auto lg:flex-1"
|
||||||
|
align="center"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 justify-center">
|
<div className="flex w-full justify-start lg:w-auto lg:justify-end lg:ml-auto">
|
||||||
<ToggleGroup
|
<ToggleGroup
|
||||||
type="single"
|
type="single"
|
||||||
value={billingFilter}
|
value={billingFilter}
|
||||||
onValueChange={(value) => value && setBillingFilter(value as typeof billingFilter)}
|
onValueChange={(value) => value && setBillingFilter(value as typeof billingFilter)}
|
||||||
className="inline-flex rounded-full border border-border/60 bg-white/80 p-1 overflow-hidden"
|
className="inline-flex rounded-full border border-border/60 bg-white/80 p-1 shadow-sm"
|
||||||
|
>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="all"
|
||||||
|
className="rounded-full px-6 py-2 text-xs font-semibold whitespace-nowrap transition first:rounded-l-full last:rounded-r-full"
|
||||||
>
|
>
|
||||||
<ToggleGroupItem value="all" className="rounded-full px-6 py-2 text-xs font-semibold whitespace-nowrap">
|
|
||||||
Todos
|
Todos
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
<ToggleGroupItem value="avulso" className="rounded-full px-6 py-2 text-xs font-semibold whitespace-nowrap">
|
<ToggleGroupItem
|
||||||
|
value="avulso"
|
||||||
|
className="rounded-full px-6 py-2 text-xs font-semibold whitespace-nowrap transition first:rounded-l-full last:rounded-r-full"
|
||||||
|
>
|
||||||
Somente avulsos
|
Somente avulsos
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
<ToggleGroupItem value="contratado" className="rounded-full px-6 py-2 text-xs font-semibold whitespace-nowrap">
|
<ToggleGroupItem
|
||||||
|
value="contratado"
|
||||||
|
className="rounded-full px-6 py-2 text-xs font-semibold whitespace-nowrap transition first:rounded-l-full last:rounded-r-full"
|
||||||
|
>
|
||||||
Somente contratados
|
Somente contratados
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
|
|
|
||||||
|
|
@ -483,7 +483,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
const hasAssignee = Boolean(currentAssigneeId)
|
const hasAssignee = Boolean(currentAssigneeId)
|
||||||
const isCurrentResponsible = hasAssignee && convexUserId ? currentAssigneeId === convexUserId : false
|
const isCurrentResponsible = hasAssignee && convexUserId ? currentAssigneeId === convexUserId : false
|
||||||
const isResolved = status === "RESOLVED"
|
const isResolved = status === "RESOLVED"
|
||||||
const canControlWork = !isResolved && (isAdmin || !hasAssignee || isCurrentResponsible)
|
const canControlWork = !isResolved && isStaff && hasAssignee
|
||||||
const canPauseWork = !isResolved && (isAdmin || isCurrentResponsible)
|
const canPauseWork = !isResolved && (isAdmin || isCurrentResponsible)
|
||||||
const pauseDisabled = !canPauseWork
|
const pauseDisabled = !canPauseWork
|
||||||
const startDisabled = !canControlWork
|
const startDisabled = !canControlWork
|
||||||
|
|
@ -491,11 +491,14 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
if (isResolved) {
|
if (isResolved) {
|
||||||
return "Este chamado está encerrado. Reabra o ticket para iniciar um novo atendimento."
|
return "Este chamado está encerrado. Reabra o ticket para iniciar um novo atendimento."
|
||||||
}
|
}
|
||||||
if (!isAdmin && hasAssignee && !isCurrentResponsible) {
|
if (!hasAssignee) {
|
||||||
return "Apenas o responsável atual ou um administrador pode iniciar este atendimento."
|
return "Defina um responsável antes de iniciar o atendimento."
|
||||||
|
}
|
||||||
|
if (!isStaff) {
|
||||||
|
return "Apenas a equipe interna pode iniciar este atendimento."
|
||||||
}
|
}
|
||||||
return "Não é possível iniciar o atendimento neste momento."
|
return "Não é possível iniciar o atendimento neste momento."
|
||||||
}, [isResolved, isAdmin, hasAssignee, isCurrentResponsible])
|
}, [isResolved, hasAssignee, isStaff])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!customersInitialized) {
|
if (!customersInitialized) {
|
||||||
|
|
@ -664,11 +667,6 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
setAssigneeSelection(currentAssigneeId)
|
setAssigneeSelection(currentAssigneeId)
|
||||||
throw new Error("invalid-assignee")
|
throw new Error("invalid-assignee")
|
||||||
} else {
|
} else {
|
||||||
if (status === "AWAITING_ATTENDANCE" || workSummary?.activeSession) {
|
|
||||||
toast.error("Pause o atendimento antes de reatribuir o chamado.", { id: "assignee" })
|
|
||||||
setAssigneeSelection(currentAssigneeId)
|
|
||||||
throw new Error("assignee-not-allowed")
|
|
||||||
}
|
|
||||||
const reasonValue = assigneeChangeReason.trim()
|
const reasonValue = assigneeChangeReason.trim()
|
||||||
if (reasonValue.length > 0 && reasonValue.length < 5) {
|
if (reasonValue.length > 0 && reasonValue.length < 5) {
|
||||||
setAssigneeReasonError("Descreva o motivo com pelo menos 5 caracteres ou deixe em branco.")
|
setAssigneeReasonError("Descreva o motivo com pelo menos 5 caracteres ou deixe em branco.")
|
||||||
|
|
@ -985,6 +983,10 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
|
|
||||||
const handleStartWork = async (workType: "INTERNAL" | "EXTERNAL") => {
|
const handleStartWork = async (workType: "INTERNAL" | "EXTERNAL") => {
|
||||||
if (!convexUserId) return
|
if (!convexUserId) return
|
||||||
|
if (!assigneeState?.id) {
|
||||||
|
toast.error("Defina um responsável antes de iniciar o atendimento.")
|
||||||
|
return
|
||||||
|
}
|
||||||
toast.dismiss("work")
|
toast.dismiss("work")
|
||||||
toast.loading("Iniciando atendimento...", { id: "work" })
|
toast.loading("Iniciando atendimento...", { id: "work" })
|
||||||
try {
|
try {
|
||||||
|
|
@ -1029,18 +1031,21 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
activeSession: null,
|
activeSession: null,
|
||||||
perAgentTotals: [],
|
perAgentTotals: [],
|
||||||
}
|
}
|
||||||
const actorId = String(convexUserId)
|
const sessionAgentId = assigneeState?.id ? String(assigneeState.id) : ""
|
||||||
|
if (!sessionAgentId) {
|
||||||
|
return base
|
||||||
|
}
|
||||||
const existingTotals = base.perAgentTotals ?? []
|
const existingTotals = base.perAgentTotals ?? []
|
||||||
const hasActorEntry = existingTotals.some((item) => item.agentId === actorId)
|
const hasAgentEntry = existingTotals.some((item) => item.agentId === sessionAgentId)
|
||||||
const updatedTotals = hasActorEntry
|
const updatedTotals = hasAgentEntry
|
||||||
? existingTotals
|
? existingTotals
|
||||||
: [
|
: [
|
||||||
...existingTotals,
|
...existingTotals,
|
||||||
{
|
{
|
||||||
agentId: actorId,
|
agentId: sessionAgentId,
|
||||||
agentName: viewerAgentMeta?.name ?? null,
|
agentName: assigneeState?.name ?? viewerAgentMeta?.name ?? null,
|
||||||
agentEmail: viewerAgentMeta?.email ?? null,
|
agentEmail: assigneeState?.email ?? viewerAgentMeta?.email ?? null,
|
||||||
avatarUrl: viewerAgentMeta?.avatarUrl ?? null,
|
avatarUrl: assigneeState?.avatarUrl ?? viewerAgentMeta?.avatarUrl ?? null,
|
||||||
totalWorkedMs: 0,
|
totalWorkedMs: 0,
|
||||||
internalWorkedMs: 0,
|
internalWorkedMs: 0,
|
||||||
externalWorkedMs: 0,
|
externalWorkedMs: 0,
|
||||||
|
|
@ -1051,7 +1056,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
serverNow: typeof resultMeta?.serverNow === "number" ? resultMeta.serverNow : getServerNow(),
|
serverNow: typeof resultMeta?.serverNow === "number" ? resultMeta.serverNow : getServerNow(),
|
||||||
activeSession: {
|
activeSession: {
|
||||||
id: (sessionId as Id<"ticketWorkSessions">) ?? (base.activeSession?.id as Id<"ticketWorkSessions">),
|
id: (sessionId as Id<"ticketWorkSessions">) ?? (base.activeSession?.id as Id<"ticketWorkSessions">),
|
||||||
agentId: actorId,
|
agentId: sessionAgentId,
|
||||||
startedAt: startedAtMs,
|
startedAt: startedAtMs,
|
||||||
workType,
|
workType,
|
||||||
},
|
},
|
||||||
|
|
@ -1060,21 +1065,6 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
})
|
})
|
||||||
|
|
||||||
setStatus("AWAITING_ATTENDANCE")
|
setStatus("AWAITING_ATTENDANCE")
|
||||||
if (viewerAgentMeta) {
|
|
||||||
setAssigneeState((prevAssignee) => {
|
|
||||||
if (prevAssignee && prevAssignee.id === viewerAgentMeta.id) {
|
|
||||||
return prevAssignee
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id: viewerAgentMeta.id,
|
|
||||||
name: viewerAgentMeta.name ?? prevAssignee?.name ?? "Responsável",
|
|
||||||
email: viewerAgentMeta.email ?? prevAssignee?.email ?? "",
|
|
||||||
avatarUrl: viewerAgentMeta.avatarUrl ?? prevAssignee?.avatarUrl ?? undefined,
|
|
||||||
teams: prevAssignee?.teams ?? [],
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setAssigneeSelection(viewerAgentMeta.id)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : "Não foi possível atualizar o atendimento"
|
const message = error instanceof Error ? error.message : "Não foi possível atualizar o atendimento"
|
||||||
toast.error(message, { id: "work" })
|
toast.error(message, { id: "work" })
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ export function TicketsFilters({
|
||||||
to={filters.dateTo}
|
to={filters.dateTo}
|
||||||
onChange={({ from, to }) => setPartial({ dateFrom: from, dateTo: to })}
|
onChange={({ from, to }) => setPartial({ dateFrom: from, dateTo: to })}
|
||||||
className="w-full min-w-[200px] rounded-2xl border-slate-300 bg-white/95 text-left text-sm font-semibold text-neutral-700 lg:w-auto"
|
className="w-full min-w-[200px] rounded-2xl border-slate-300 bg-white/95 text-left text-sm font-semibold text-neutral-700 lg:w-auto"
|
||||||
|
align="center"
|
||||||
/>
|
/>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
|
|
@ -253,6 +254,7 @@ export function TicketsFilters({
|
||||||
clearLabel="Todas as empresas"
|
clearLabel="Todas as empresas"
|
||||||
triggerClassName={fieldTrigger}
|
triggerClassName={fieldTrigger}
|
||||||
prefix={<IconBuilding className="size-4 text-neutral-400" />}
|
prefix={<IconBuilding className="size-4 text-neutral-400" />}
|
||||||
|
align="center"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ type SearchableComboboxProps = {
|
||||||
scrollClassName?: string
|
scrollClassName?: string
|
||||||
scrollProps?: React.HTMLAttributes<HTMLDivElement>
|
scrollProps?: React.HTMLAttributes<HTMLDivElement>
|
||||||
prefix?: ReactNode
|
prefix?: ReactNode
|
||||||
|
align?: "left" | "center"
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SearchableCombobox({
|
export function SearchableCombobox({
|
||||||
|
|
@ -54,6 +55,7 @@ export function SearchableCombobox({
|
||||||
scrollClassName,
|
scrollClassName,
|
||||||
scrollProps,
|
scrollProps,
|
||||||
prefix,
|
prefix,
|
||||||
|
align = "left",
|
||||||
}: SearchableComboboxProps) {
|
}: SearchableComboboxProps) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("")
|
||||||
|
|
@ -109,7 +111,7 @@ export function SearchableCombobox({
|
||||||
>
|
>
|
||||||
<span className="flex flex-1 items-center gap-2">
|
<span className="flex flex-1 items-center gap-2">
|
||||||
{prefix ? <span className="inline-flex items-center text-neutral-400">{prefix}</span> : null}
|
{prefix ? <span className="inline-flex items-center text-neutral-400">{prefix}</span> : null}
|
||||||
<span className="flex-1 truncate text-left">
|
<span className={cn("flex-1 truncate", align === "center" ? "text-center" : "text-left")}>
|
||||||
{renderValue ? (
|
{renderValue ? (
|
||||||
renderValue(selected)
|
renderValue(selected)
|
||||||
) : selected?.label ? (
|
) : selected?.label ? (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Ticket } from "@/lib/schemas/ticket"
|
import type { Ticket } from "@/lib/schemas/ticket"
|
||||||
|
|
||||||
export const VISIT_KEYWORDS = ["visita", "visitas", "in loco", "laboratório", "laboratorio", "lab"]
|
export const VISIT_KEYWORDS = ["visita", "visitas", "in loco"]
|
||||||
|
|
||||||
export function isVisitTicket(ticket: Ticket): boolean {
|
export function isVisitTicket(ticket: Ticket): boolean {
|
||||||
const queueName = ticket.queue?.toLowerCase() ?? ""
|
const queueName = ticket.queue?.toLowerCase() ?? ""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue