chore: prep platform improvements
This commit is contained in:
parent
a62f3d5283
commit
c5ddd54a3e
24 changed files with 777 additions and 649 deletions
|
|
@ -17,6 +17,7 @@ import { Checkbox } from "@/components/ui/checkbox"
|
|||
import { Badge } from "@/components/ui/badge"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { SearchableCombobox, type SearchableComboboxOption } from "@/components/ui/searchable-combobox"
|
||||
|
||||
type FieldOption = { value: string; label: string }
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ type Field = {
|
|||
options: FieldOption[]
|
||||
order: number
|
||||
scope: string
|
||||
companyId: string | null
|
||||
}
|
||||
|
||||
const TYPE_LABELS: Record<Field["type"], string> = {
|
||||
|
|
@ -54,6 +56,11 @@ export function FieldsManager() {
|
|||
convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip"
|
||||
) as Array<{ id: string; key: string; label: string }> | undefined
|
||||
|
||||
const companies = useQuery(
|
||||
api.companies.list,
|
||||
convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip"
|
||||
) as Array<{ id: string; name: string; slug?: string }> | undefined
|
||||
|
||||
const scopeOptions = useMemo(
|
||||
() => [
|
||||
{ value: "all", label: "Todos os formulários" },
|
||||
|
|
@ -62,6 +69,28 @@ export function FieldsManager() {
|
|||
[templates]
|
||||
)
|
||||
|
||||
const companyOptions = useMemo<SearchableComboboxOption[]>(() => {
|
||||
if (!companies) return []
|
||||
return companies
|
||||
.map((company) => ({
|
||||
value: company.id,
|
||||
label: company.name,
|
||||
description: company.slug ?? undefined,
|
||||
keywords: company.slug ? [company.slug] : [],
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label, "pt-BR"))
|
||||
}, [companies])
|
||||
|
||||
const companyLabelById = useMemo(() => {
|
||||
const map = new Map<string, string>()
|
||||
companyOptions.forEach((option) => map.set(option.value, option.label))
|
||||
return map
|
||||
}, [companyOptions])
|
||||
|
||||
const companyComboboxOptions = useMemo<SearchableComboboxOption[]>(() => {
|
||||
return [{ value: "all", label: "Todas as empresas" }, ...companyOptions]
|
||||
}, [companyOptions])
|
||||
|
||||
const templateLabelByKey = useMemo(() => {
|
||||
const map = new Map<string, string>()
|
||||
templates?.forEach((tpl) => map.set(tpl.key, tpl.label))
|
||||
|
|
@ -79,9 +108,11 @@ export function FieldsManager() {
|
|||
const [required, setRequired] = useState(false)
|
||||
const [options, setOptions] = useState<FieldOption[]>([])
|
||||
const [scopeSelection, setScopeSelection] = useState<string>("all")
|
||||
const [companySelection, setCompanySelection] = useState<string>("all")
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [editingField, setEditingField] = useState<Field | null>(null)
|
||||
const [editingScope, setEditingScope] = useState<string>("all")
|
||||
const [editingCompanySelection, setEditingCompanySelection] = useState<string>("all")
|
||||
|
||||
const totals = useMemo(() => {
|
||||
if (!fields) return { total: 0, required: 0, select: 0 }
|
||||
|
|
@ -99,6 +130,8 @@ export function FieldsManager() {
|
|||
setRequired(false)
|
||||
setOptions([])
|
||||
setScopeSelection("all")
|
||||
setCompanySelection("all")
|
||||
setEditingCompanySelection("all")
|
||||
}
|
||||
|
||||
const normalizeOptions = (source: FieldOption[]) =>
|
||||
|
|
@ -121,6 +154,7 @@ export function FieldsManager() {
|
|||
}
|
||||
const preparedOptions = type === "select" ? normalizeOptions(options) : undefined
|
||||
const scopeValue = scopeSelection === "all" ? undefined : scopeSelection
|
||||
const companyIdValue = companySelection === "all" ? undefined : (companySelection as Id<"companies">)
|
||||
setSaving(true)
|
||||
toast.loading("Criando campo...", { id: "field" })
|
||||
try {
|
||||
|
|
@ -133,6 +167,7 @@ export function FieldsManager() {
|
|||
required,
|
||||
options: preparedOptions,
|
||||
scope: scopeValue,
|
||||
companyId: companyIdValue,
|
||||
})
|
||||
toast.success("Campo criado", { id: "field" })
|
||||
resetForm()
|
||||
|
|
@ -173,6 +208,7 @@ export function FieldsManager() {
|
|||
setRequired(field.required)
|
||||
setOptions(field.options)
|
||||
setEditingScope(field.scope ?? "all")
|
||||
setEditingCompanySelection(field.companyId ?? "all")
|
||||
}
|
||||
|
||||
const handleUpdate = async () => {
|
||||
|
|
@ -187,6 +223,7 @@ export function FieldsManager() {
|
|||
}
|
||||
const preparedOptions = type === "select" ? normalizeOptions(options) : undefined
|
||||
const scopeValue = editingScope === "all" ? undefined : editingScope
|
||||
const companyIdValue = editingCompanySelection === "all" ? undefined : (editingCompanySelection as Id<"companies">)
|
||||
setSaving(true)
|
||||
toast.loading("Atualizando campo...", { id: "field-edit" })
|
||||
try {
|
||||
|
|
@ -200,6 +237,7 @@ export function FieldsManager() {
|
|||
required,
|
||||
options: preparedOptions,
|
||||
scope: scopeValue,
|
||||
companyId: companyIdValue,
|
||||
})
|
||||
toast.success("Campo atualizado", { id: "field-edit" })
|
||||
setEditingField(null)
|
||||
|
|
@ -347,6 +385,25 @@ export function FieldsManager() {
|
|||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Empresa (opcional)</Label>
|
||||
<SearchableCombobox
|
||||
value={companySelection}
|
||||
onValueChange={(value) => setCompanySelection(value ?? "all")}
|
||||
options={companyComboboxOptions}
|
||||
placeholder="Todas as empresas"
|
||||
renderValue={(option) =>
|
||||
option ? (
|
||||
<span className="truncate">{option.label}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">Todas as empresas</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-neutral-500">
|
||||
Selecione uma empresa para tornar este campo exclusivo dela. Sem seleção, o campo aparecerá em todos os tickets.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
|
|
@ -443,9 +500,14 @@ export function FieldsManager() {
|
|||
) : null}
|
||||
</div>
|
||||
<CardDescription className="text-neutral-600">Identificador: {field.key}</CardDescription>
|
||||
<Badge variant="secondary" className="rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-neutral-700">
|
||||
{scopeLabel}
|
||||
</Badge>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="secondary" className="rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-neutral-700">
|
||||
{scopeLabel}
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-neutral-700">
|
||||
{field.companyId ? `Empresa: ${companyLabelById.get(field.companyId) ?? "Específica"}` : "Todas as empresas"}
|
||||
</Badge>
|
||||
</div>
|
||||
{field.description ? (
|
||||
<p className="text-sm text-neutral-600">{field.description}</p>
|
||||
) : null}
|
||||
|
|
@ -554,6 +616,25 @@ export function FieldsManager() {
|
|||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Empresa (opcional)</Label>
|
||||
<SearchableCombobox
|
||||
value={editingCompanySelection}
|
||||
onValueChange={(value) => setEditingCompanySelection(value ?? "all")}
|
||||
options={companyComboboxOptions}
|
||||
placeholder="Todas as empresas"
|
||||
renderValue={(option) =>
|
||||
option ? (
|
||||
<span className="truncate">{option.label}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">Todas as empresas</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-neutral-500">
|
||||
Defina uma empresa para restringir este campo apenas aos tickets dela.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue