Align admin tables with ticket styling and add board view

This commit is contained in:
Esdras Renan 2025-10-24 12:23:27 -03:00
parent 63cf9f9d45
commit a319aa0eff
8 changed files with 783 additions and 447 deletions

View file

@ -89,7 +89,6 @@ function RequesterPreview({ customer, company }: RequesterPreviewProps) {
}
const NO_COMPANY_VALUE = "__no_company__"
const AUTO_COMPANY_VALUE = "__auto__"
const schema = z.object({
subject: z.string().default(""),
@ -118,7 +117,7 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
channel: "MANUAL",
queueName: null,
assigneeId: null,
companyId: AUTO_COMPANY_VALUE,
companyId: NO_COMPANY_VALUE,
requesterId: "",
categoryId: "",
subcategoryId: "",
@ -180,7 +179,7 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
const queueValue = form.watch("queueName") ?? "NONE"
const assigneeValue = form.watch("assigneeId") ?? null
const assigneeSelectValue = assigneeValue ?? "NONE"
const companyValue = form.watch("companyId") ?? AUTO_COMPANY_VALUE
const companyValue = form.watch("companyId") ?? NO_COMPANY_VALUE
const requesterValue = form.watch("requesterId") ?? ""
const categoryIdValue = form.watch("categoryId")
const subcategoryIdValue = form.watch("subcategoryId")
@ -188,18 +187,25 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
const companyOptions = useMemo(() => {
const map = new Map<string, { id: string; name: string; isAvulso?: boolean; keywords: string[] }>()
companies.forEach((company) => {
const trimmedName = company.name.trim()
const slugFallback = company.slug?.trim()
const label =
trimmedName.length > 0 ? trimmedName : slugFallback && slugFallback.length > 0 ? slugFallback : `Empresa ${company.id.slice(0, 8)}`
map.set(company.id, {
id: company.id,
name: company.name.trim().length > 0 ? company.name : "Empresa sem nome",
name: label,
isAvulso: false,
keywords: company.slug ? [company.slug] : [],
})
})
customers.forEach((customer) => {
if (customer.companyId && !map.has(customer.companyId)) {
const trimmedName = customer.companyName?.trim() ?? ""
const label =
trimmedName.length > 0 ? trimmedName : `Empresa ${customer.companyId.slice(0, 8)}`
map.set(customer.companyId, {
id: customer.companyId,
name: customer.companyName && customer.companyName.trim().length > 0 ? customer.companyName : "Empresa sem nome",
name: label,
isAvulso: customer.companyIsAvulso,
keywords: [],
})
@ -208,12 +214,12 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
const base: Array<{ id: string; name: string; isAvulso?: boolean; keywords: string[] }> = [
{ id: NO_COMPANY_VALUE, name: "Sem empresa", keywords: ["sem empresa", "nenhuma"], isAvulso: false },
]
const sorted = Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name, "pt-BR"))
const sorted = Array.from(map.values())
.sort((a, b) => a.name.localeCompare(b.name, "pt-BR"))
return [...base, ...sorted]
}, [companies, customers])
const filteredCustomers = useMemo(() => {
if (companyValue === AUTO_COMPANY_VALUE) return customers
if (companyValue === NO_COMPANY_VALUE) {
return customers.filter((customer) => !customer.companyId)
}
@ -236,11 +242,10 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
[companyOptions],
)
const selectedCompanyOption = useMemo(() => {
if (companyValue === AUTO_COMPANY_VALUE) return null
const key = companyValue === NO_COMPANY_VALUE ? NO_COMPANY_VALUE : companyValue
return companyOptionMap.get(key) ?? null
}, [companyOptionMap, companyValue])
const selectedCompanyOption = useMemo(
() => companyOptionMap.get(companyValue) ?? null,
[companyOptionMap, companyValue],
)
const requesterById = useMemo(
() => new Map(customers.map((customer) => [customer.id, customer])),
@ -275,7 +280,7 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
useEffect(() => {
if (!open) {
setCustomersInitialized(false)
form.setValue("companyId", AUTO_COMPANY_VALUE, { shouldDirty: false, shouldTouch: false })
form.setValue("companyId", NO_COMPANY_VALUE, { shouldDirty: false, shouldTouch: false })
form.setValue("requesterId", "", { shouldDirty: false, shouldTouch: false })
return
}
@ -294,7 +299,7 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
if (selected?.companyId) {
form.setValue("companyId", selected.companyId, { shouldDirty: false, shouldTouch: false })
} else {
form.setValue("companyId", selected ? NO_COMPANY_VALUE : AUTO_COMPANY_VALUE, { shouldDirty: false, shouldTouch: false })
form.setValue("companyId", NO_COMPANY_VALUE, { shouldDirty: false, shouldTouch: false })
}
setCustomersInitialized(true)
}, [open, customersInitialized, customers, convexUserId, form])
@ -558,15 +563,13 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
<Field>
<FieldLabel>Empresa</FieldLabel>
<SearchableCombobox
value={companyValue === AUTO_COMPANY_VALUE ? null : companyValue}
value={companyValue}
onValueChange={(nextValue) => {
const normalizedValue = nextValue ?? AUTO_COMPANY_VALUE
const normalizedValue = nextValue ?? NO_COMPANY_VALUE
const nextCustomers =
normalizedValue === AUTO_COMPANY_VALUE
? customers
: normalizedValue === NO_COMPANY_VALUE
? customers.filter((customer) => !customer.companyId)
: customers.filter((customer) => customer.companyId === normalizedValue)
normalizedValue === NO_COMPANY_VALUE
? customers.filter((customer) => !customer.companyId)
: customers.filter((customer) => customer.companyId === normalizedValue)
form.setValue("companyId", normalizedValue, {
shouldDirty: normalizedValue !== companyValue,
shouldTouch: true,
@ -588,8 +591,6 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
}}
options={companyComboboxOptions}
placeholder="Selecionar empresa"
allowClear
clearLabel="Qualquer empresa"
renderValue={(option) =>
option ? (
<span className="truncate">{option.label}</span>