feat(export,tickets,forms,emails):\n- Corrige scroll de Dialogs e melhora UI de seleção de colunas (ícones e separador)\n- Ajusta rota/params da exportação em massa e adiciona modal de exportação individual\n- Renomeia 'Chamado padrão' para 'Chamado' e garante visibilidade total para admin/agente\n- Adiciona toggles por empresa/usuário para habilitar Admissão/Desligamento\n- Exibe badge do tipo de solicitação na listagem e no cabeçalho do ticket\n- Prepara notificações por e-mail (comentário público e encerramento) via SMTP\n

This commit is contained in:
codex-bot 2025-11-04 13:41:32 -03:00
parent a8333c010f
commit 06deb99bcd
12 changed files with 543 additions and 17 deletions

View file

@ -80,7 +80,9 @@ import { Textarea } from "@/components/ui/textarea"
import { TimePicker } from "@/components/ui/time-picker"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
import { useQuery } from "convex/react"
import { useQuery, useMutation } from "convex/react"
import { useAuth } from "@/lib/auth-client"
import type { Id } from "@/convex/_generated/dataModel"
import { api } from "@/convex/_generated/api"
import { MultiValueInput } from "@/components/ui/multi-value-input"
import { AdminDevicesOverview } from "@/components/admin/devices/admin-devices-overview"
@ -1685,6 +1687,13 @@ function CompanySheet({ tenantId, editor, onClose, onCreated, onUpdated }: Compa
</AccordionContent>
</AccordionItem>
<AccordionItem value="requestTypes" className="rounded-lg border border-border/60 bg-muted/20 px-4">
<AccordionTrigger className="py-3 font-semibold">Tipos de solicitação</AccordionTrigger>
<AccordionContent className="pb-5">
<CompanyRequestTypesControls tenantId={tenantId} companyId={editor?.mode === "edit" ? editor.company.id : null} />
</AccordionContent>
</AccordionItem>
{editor?.mode === "edit" ? (
<AccordionItem value="machines" className="rounded-lg border border-border/60 bg-muted/20 px-4">
<AccordionTrigger className="py-3 font-semibold">Dispositivos vinculadas</AccordionTrigger>
@ -2173,3 +2182,69 @@ function BusinessHoursEditor({ form }: BusinessHoursEditorProps) {
</div>
)
}
type CompanyRequestTypesControlsProps = { tenantId?: string | null; companyId: string | null }
function CompanyRequestTypesControls({ tenantId, companyId }: CompanyRequestTypesControlsProps) {
const { convexUserId } = useAuth()
const canLoad = Boolean(tenantId && convexUserId)
const settings = useQuery(
api.ticketFormSettings.list,
canLoad ? { tenantId: tenantId as string, viewerId: convexUserId as Id<"users"> } : "skip"
) as Array<{ template: string; scope: string; companyId?: string | null; enabled: boolean; updatedAt: number }> | undefined
const upsert = useMutation(api.ticketFormSettings.upsert)
const resolveEnabled = (template: "admissao" | "desligamento") => {
const scoped = (settings ?? []).filter((s) => s.template === template)
const base = true
if (!companyId) return base
const latest = scoped
.filter((s) => s.scope === "company" && String(s.companyId ?? "") === String(companyId))
.sort((a, b) => b.updatedAt - a.updatedAt)[0]
return typeof latest?.enabled === "boolean" ? latest.enabled : base
}
const admissaoEnabled = resolveEnabled("admissao")
const desligamentoEnabled = resolveEnabled("desligamento")
const handleToggle = async (template: "admissao" | "desligamento", enabled: boolean) => {
if (!tenantId || !convexUserId || !companyId) return
try {
await upsert({
tenantId,
actorId: convexUserId as Id<"users">,
template,
scope: "company",
companyId: companyId as unknown as Id<"companies">,
enabled,
})
toast.success("Configuração salva.")
} catch (error) {
console.error("Falha ao salvar configuração de formulário", error)
toast.error("Não foi possível salvar a configuração.")
}
}
return (
<div className="space-y-3">
<p className="text-sm text-muted-foreground">Defina quais tipos de solicitação estão disponíveis para colaboradores/gestores desta empresa. Administradores e agentes sempre veem todas as opções.</p>
<div className="grid gap-3 sm:grid-cols-2">
<label className="flex items-center gap-2 text-sm text-foreground">
<Checkbox
checked={admissaoEnabled}
onCheckedChange={(v) => handleToggle("admissao", Boolean(v))}
disabled={!companyId}
/>
<span>Admissão de colaborador</span>
</label>
<label className="flex items-center gap-2 text-sm text-foreground">
<Checkbox
checked={desligamentoEnabled}
onCheckedChange={(v) => handleToggle("desligamento", Boolean(v))}
disabled={!companyId}
/>
<span>Desligamento de colaborador</span>
</label>
</div>
</div>
)
}