desktop(portal): ocultar filtros avançados (fila/empresa/responsável) para colaboradores e gestores quando aberto via app desktop; manter categoria/status/ordenação/período; docs atualizados

This commit is contained in:
Esdras Renan 2025-11-13 13:04:17 -03:00
parent cc68c85246
commit 5b1d73ea43
3 changed files with 78 additions and 49 deletions

View file

@ -0,0 +1,12 @@
Alterações em 13/11/2025 — Portal no app desktop
- Ocultamos filtros avançados em Portal ▸ Meus chamados quando o acesso vem do app desktop (WebView com sessão de máquina) e o usuário final é `collaborator` ou `manager`.
- Filtros ocultos: Fila, Empresa, Responsável.
- Filtros mantidos: Categoria, Status, Ordenação e Período.
- Implementação:
- `src/components/portal/portal-ticket-filters.tsx`: adicionada prop opcional `hideAdvancedFilters` para suprimir os três filtros mencionados sem quebrar o layout.
- `src/components/portal/portal-ticket-list.tsx`: definimos `hideAdvancedFilters` como `true` quando `machineContext` está presente e a `role` efetiva é `collaborator` ou `manager`.
- Observações:
- A detecção do app desktop usa o `machineContext` carregado via `/api/machines/session` (não foi necessário cookie adicional).
- A mudança afeta somente o portal aberto pelo desktop; no navegador os filtros permanecem inalterados.

View file

@ -40,6 +40,12 @@ type PortalTicketFiltersProps = {
companies: string[] companies: string[]
categories: Option[] categories: Option[]
assignees: Option[] assignees: Option[]
/**
* Quando verdadeiro, oculta filtros avançados que não devem aparecer para
* usuários finais no app desktop (fila, empresa e responsável).
* Mantemos categoria, status, ordenação e período.
*/
hideAdvancedFilters?: boolean
} }
export function PortalTicketFilters({ export function PortalTicketFilters({
@ -50,6 +56,7 @@ export function PortalTicketFilters({
companies, companies,
categories, categories,
assignees, assignees,
hideAdvancedFilters = false,
}: PortalTicketFiltersProps) { }: PortalTicketFiltersProps) {
const handleChange = (partial: Partial<PortalTicketFiltersState>) => { const handleChange = (partial: Partial<PortalTicketFiltersState>) => {
onFiltersChange(partial) onFiltersChange(partial)
@ -58,38 +65,42 @@ export function PortalTicketFilters({
return ( return (
<div className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm"> <div className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm">
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
<Select {!hideAdvancedFilters && (
value={filters.queue ?? ALL_VALUE} <Select
onValueChange={(value) => handleChange({ queue: value === ALL_VALUE ? null : value })} value={filters.queue ?? ALL_VALUE}
> onValueChange={(value) => handleChange({ queue: value === ALL_VALUE ? null : value })}
<SelectTrigger className="w-full"> >
<SelectValue placeholder="Todas as filas" /> <SelectTrigger className="w-full">
</SelectTrigger> <SelectValue placeholder="Todas as filas" />
<SelectContent> </SelectTrigger>
<SelectItem value={ALL_VALUE}>Todas as filas</SelectItem> <SelectContent>
{queues.map((queue) => ( <SelectItem value={ALL_VALUE}>Todas as filas</SelectItem>
<SelectItem key={queue} value={queue}> {queues.map((queue) => (
{queue} <SelectItem key={queue} value={queue}>
</SelectItem> {queue}
))} </SelectItem>
</SelectContent> ))}
</Select> </SelectContent>
<Select </Select>
value={filters.company ?? ALL_VALUE} )}
onValueChange={(value) => handleChange({ company: value === ALL_VALUE ? null : value })} {!hideAdvancedFilters && (
> <Select
<SelectTrigger className="w-full"> value={filters.company ?? ALL_VALUE}
<SelectValue placeholder="Todas as empresas" /> onValueChange={(value) => handleChange({ company: value === ALL_VALUE ? null : value })}
</SelectTrigger> >
<SelectContent> <SelectTrigger className="w-full">
<SelectItem value={ALL_VALUE}>Todas as empresas</SelectItem> <SelectValue placeholder="Todas as empresas" />
{companies.map((company) => ( </SelectTrigger>
<SelectItem key={company} value={company}> <SelectContent>
{company} <SelectItem value={ALL_VALUE}>Todas as empresas</SelectItem>
</SelectItem> {companies.map((company) => (
))} <SelectItem key={company} value={company}>
</SelectContent> {company}
</Select> </SelectItem>
))}
</SelectContent>
</Select>
)}
<Select <Select
value={filters.categoryId ?? ALL_VALUE} value={filters.categoryId ?? ALL_VALUE}
onValueChange={(value) => handleChange({ categoryId: value === ALL_VALUE ? null : value })} onValueChange={(value) => handleChange({ categoryId: value === ALL_VALUE ? null : value })}
@ -106,22 +117,24 @@ export function PortalTicketFilters({
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
<Select {!hideAdvancedFilters && (
value={filters.assigneeId ?? ALL_VALUE} <Select
onValueChange={(value) => handleChange({ assigneeId: value === ALL_VALUE ? null : value })} value={filters.assigneeId ?? ALL_VALUE}
> onValueChange={(value) => handleChange({ assigneeId: value === ALL_VALUE ? null : value })}
<SelectTrigger className="w-full"> >
<SelectValue placeholder="Todos os responsáveis" /> <SelectTrigger className="w-full">
</SelectTrigger> <SelectValue placeholder="Todos os responsáveis" />
<SelectContent> </SelectTrigger>
<SelectItem value={ALL_VALUE}>Todos os responsáveis</SelectItem> <SelectContent>
{assignees.map((assignee) => ( <SelectItem value={ALL_VALUE}>Todos os responsáveis</SelectItem>
<SelectItem key={assignee.id} value={assignee.id}> {assignees.map((assignee) => (
{assignee.name} <SelectItem key={assignee.id} value={assignee.id}>
</SelectItem> {assignee.name}
))} </SelectItem>
</SelectContent> ))}
</Select> </SelectContent>
</Select>
)}
<Select value={filters.status} onValueChange={(value) => handleChange({ status: value as PortalTicketFiltersState["status"] })}> <Select value={filters.status} onValueChange={(value) => handleChange({ status: value as PortalTicketFiltersState["status"] })}>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Status" /> <SelectValue placeholder="Status" />

View file

@ -21,7 +21,7 @@ import {
} from "@/components/portal/portal-ticket-filters" } from "@/components/portal/portal-ticket-filters"
export function PortalTicketList() { export function PortalTicketList() {
const { convexUserId, session, machineContext } = useAuth() const { convexUserId, session, machineContext, role } = useAuth()
const viewerId = (convexUserId ?? machineContext?.assignedUserId ?? null) as Id<"users"> | null const viewerId = (convexUserId ?? machineContext?.assignedUserId ?? null) as Id<"users"> | null
@ -43,6 +43,9 @@ export function PortalTicketList() {
const [filters, setFilters] = useState<PortalTicketFiltersState>(defaultPortalTicketFilters) const [filters, setFilters] = useState<PortalTicketFiltersState>(defaultPortalTicketFilters)
// No app desktop, colaboradores e gestores não devem ver filtros avançados
const hideAdvancedFilters = Boolean(machineContext) && (role === "collaborator" || role === "manager")
const queueOptions = useMemo(() => { const queueOptions = useMemo(() => {
const set = new Set<string>() const set = new Set<string>()
;(tickets as Ticket[]).forEach((ticket) => { ;(tickets as Ticket[]).forEach((ticket) => {
@ -214,6 +217,7 @@ export function PortalTicketList() {
companies={companyOptions} companies={companyOptions}
categories={categoryOptions} categories={categoryOptions}
assignees={assigneeOptions} assignees={assigneeOptions}
hideAdvancedFilters={hideAdvancedFilters}
/> />
{filteredTickets.length === 0 ? ( {filteredTickets.length === 0 ? (
<Card className="rounded-2xl border border-slate-200 bg-white py-8 text-center shadow-sm"> <Card className="rounded-2xl border border-slate-200 bg-white py-8 text-center shadow-sm">