Ajusta placeholders, formulários e widgets

This commit is contained in:
Esdras Renan 2025-11-06 23:13:41 -03:00
parent 343f0c8c64
commit b94cea2f9a
33 changed files with 2122 additions and 462 deletions

View file

@ -1,6 +1,6 @@
"use client"
import { useCallback, useEffect, useMemo, useState, useTransition } from "react"
import { useCallback, useEffect, useMemo, useRef, useState, useTransition } from "react"
import { Controller, FormProvider, useFieldArray, useForm, type UseFormReturn } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import {
@ -80,6 +80,7 @@ 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 { Skeleton } from "@/components/ui/skeleton"
import { useQuery, useMutation } from "convex/react"
import { useAuth } from "@/lib/auth-client"
import type { Id } from "@/convex/_generated/dataModel"
@ -1691,6 +1692,7 @@ function CompanySheet({ tenantId, editor, onClose, onCreated, onUpdated }: Compa
<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} />
<CompanyExportTemplateSelector tenantId={tenantId} companyId={editor?.mode === "edit" ? editor.company.id : null} />
</AccordionContent>
</AccordionItem>
@ -2187,13 +2189,28 @@ type CompanyRequestTypesControlsProps = { tenantId?: string | null; companyId: s
function CompanyRequestTypesControls({ tenantId, companyId }: CompanyRequestTypesControlsProps) {
const { convexUserId } = useAuth()
const canLoad = Boolean(tenantId && convexUserId)
const ensureDefaults = useMutation(api.tickets.ensureTicketFormDefaults)
const hasEnsuredRef = useRef(false)
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 templates = useQuery(
api.ticketFormTemplates.listActive,
canLoad ? { tenantId: tenantId as string, viewerId: convexUserId as Id<"users"> } : "skip"
) as Array<{ key: string; label: string }> | undefined
const upsert = useMutation(api.ticketFormSettings.upsert)
const resolveEnabled = (template: "admissao" | "desligamento") => {
useEffect(() => {
if (!tenantId || !convexUserId || hasEnsuredRef.current) return
hasEnsuredRef.current = true
ensureDefaults({ tenantId, actorId: convexUserId as Id<"users"> }).catch((error) => {
console.error("Falha ao garantir formulários padrão", error)
hasEnsuredRef.current = false
})
}, [ensureDefaults, tenantId, convexUserId])
const resolveEnabled = (template: string) => {
const scoped = (settings ?? []).filter((s) => s.template === template)
const base = true
if (!companyId) return base
@ -2203,10 +2220,7 @@ function CompanyRequestTypesControls({ tenantId, companyId }: CompanyRequestType
return typeof latest?.enabled === "boolean" ? latest.enabled : base
}
const admissaoEnabled = resolveEnabled("admissao")
const desligamentoEnabled = resolveEnabled("desligamento")
const handleToggle = async (template: "admissao" | "desligamento", enabled: boolean) => {
const handleToggle = async (template: string, enabled: boolean) => {
if (!tenantId || !convexUserId || !companyId) return
try {
await upsert({
@ -2227,24 +2241,113 @@ function CompanyRequestTypesControls({ tenantId, companyId }: CompanyRequestType
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>
{!templates ? (
<div className="space-y-2">
<Skeleton className="h-10 w-full rounded-lg" />
<Skeleton className="h-10 w-full rounded-lg" />
</div>
) : templates.length === 0 ? (
<p className="text-sm text-neutral-500">Nenhum formulário disponível.</p>
) : (
<div className="grid gap-3 sm:grid-cols-2">
{templates.map((template) => {
const enabled = resolveEnabled(template.key)
return (
<label key={template.key} className="flex items-center gap-2 text-sm text-foreground">
<Checkbox
checked={enabled}
onCheckedChange={(v) => handleToggle(template.key, Boolean(v))}
disabled={!companyId}
/>
<span>{template.label}</span>
</label>
)
})}
</div>
)}
</div>
)
}
type CompanyExportTemplateSelectorProps = { tenantId?: string | null; companyId: string | null }
function CompanyExportTemplateSelector({ tenantId, companyId }: CompanyExportTemplateSelectorProps) {
const { convexUserId } = useAuth()
const canLoad = Boolean(tenantId && convexUserId)
const templates = useQuery(
api.deviceExportTemplates.list,
canLoad
? {
tenantId: tenantId as string,
viewerId: convexUserId as Id<"users">,
companyId: companyId ? (companyId as unknown as Id<"companies">) : undefined,
includeInactive: true,
}
: "skip"
) as Array<{ id: string; name: string; companyId: string | null; isDefault: boolean; description?: string }> | undefined
const setDefaultTemplate = useMutation(api.deviceExportTemplates.setDefault)
const clearDefaultTemplate = useMutation(api.deviceExportTemplates.clearCompanyDefault)
const companyTemplates = useMemo(() => {
if (!templates || !companyId) return []
return templates.filter((tpl) => String(tpl.companyId ?? "") === String(companyId))
}, [templates, companyId])
const companyDefault = useMemo(() => companyTemplates.find((tpl) => tpl.isDefault) ?? null, [companyTemplates])
const handleChange = async (value: string) => {
if (!tenantId || !convexUserId || !companyId) return
try {
if (value === "inherit") {
await clearDefaultTemplate({
tenantId,
actorId: convexUserId as Id<"users">,
companyId: companyId as unknown as Id<"companies">,
})
toast.success("Template desta empresa voltou a herdar o padrão global.")
} else {
await setDefaultTemplate({
tenantId,
actorId: convexUserId as Id<"users">,
templateId: value as Id<"deviceExportTemplates">,
})
toast.success("Template aplicado para esta empresa.")
}
} catch (error) {
console.error("Falha ao definir template de exportação", error)
toast.error("Não foi possível atualizar o template.")
}
}
const selectValue = companyDefault ? companyDefault.id : "inherit"
return (
<div className="space-y-3">
<p className="text-sm text-muted-foreground">
Defina o template padrão das exportações de inventário para esta empresa. Ao herdar, o template global será utilizado.
</p>
{!companyId ? (
<p className="text-xs text-neutral-500">Salve a empresa antes de configurar o template.</p>
) : !templates ? (
<Skeleton className="h-10 w-full rounded-md" />
) : companyTemplates.length === 0 ? (
<p className="text-xs text-neutral-500">
Nenhum template específico para esta empresa. Crie um template em <span className="font-semibold">Dispositivos &gt; Exportações</span> e associe a esta empresa para habilitar aqui.
</p>
) : (
<Select value={selectValue} onValueChange={handleChange} disabled={!companyId}>
<SelectTrigger>
<SelectValue placeholder="Herdar template global" />
</SelectTrigger>
<SelectContent>
<SelectItem value="inherit">Herdar template global</SelectItem>
{companyTemplates.map((tpl) => (
<SelectItem key={tpl.id} value={tpl.id}>
{tpl.name}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
)
}