diff --git a/src/app/(authenticated)/admin/food/_components/orders-report-modal.tsx b/src/app/(authenticated)/admin/food/_components/orders-report-modal.tsx index 242216d..4426440 100644 --- a/src/app/(authenticated)/admin/food/_components/orders-report-modal.tsx +++ b/src/app/(authenticated)/admin/food/_components/orders-report-modal.tsx @@ -104,6 +104,9 @@ export function OrdersReportModal({ // Seleção de itens na tabela para cálculo da soma e exportação const [deselectedOrderIds, setDeselectedOrderIds] = useState>(new Set()) + // Opção de agrupar por usuário (ativo por padrão) + const [groupByUser, setGroupByUser] = useState(true) + // Paginação const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(25) @@ -157,6 +160,7 @@ export function OrdersReportModal({ setSelectedUserIds([]) setUserSearchText(initialUserSearch ? initialUserSearch.trim() : "") setDeselectedOrderIds(new Set()) + setGroupByUser(true) setPage(1) } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -196,6 +200,85 @@ export function OrdersReportModal({ const isAllSelected = orders.length > 0 && selectedOrders.length === orders.length const isSomeSelected = selectedOrders.length > 0 && selectedOrders.length < orders.length + // Agrupamento de pedidos por colaborador + const groupedUsers = useMemo(() => { + const map = new Map< + string, + { + userId: string + name: string + email: string + matricula: string + setor: string + empresa: string + filial: string + orders: typeof orders + } + >() + + for (const order of orders) { + const user = order.user + const key = user?.id ?? order.userId ?? "unknown" + const existing = map.get(key) + if (existing) { + existing.orders.push(order) + } else { + const matricula = user?.matricula ?? "-" + const name = + `${user?.firstName ?? ""} ${user?.lastName ?? ""}`.trim() || + user?.email || + "Não identificado" + const email = user?.email ?? "" + const setor = user?.setor ?? "-" + const empresa = user?.filial?.empresa?.name ?? user?.enterprise ?? "-" + const filial = user?.filial ? `${user.filial.name} (${user.filial.code})` : "" + + map.set(key, { + userId: key, + name, + email, + matricula, + setor, + empresa, + filial, + orders: [order], + }) + } + } + + return Array.from(map.values()) + .map((item) => { + const selectedOrdersOfUser = item.orders.filter((o) => !deselectedOrderIds.has(o.id)) + const totalValue = item.orders.reduce( + (acc, o) => acc + (o.unitPrice ?? o.menuItem?.price ?? 0), + 0, + ) + const selectedTotalValue = selectedOrdersOfUser.reduce( + (acc, o) => acc + (o.unitPrice ?? o.menuItem?.price ?? 0), + 0, + ) + const isUserAllSelected = + selectedOrdersOfUser.length === item.orders.length && item.orders.length > 0 + const isUserSomeSelected = + selectedOrdersOfUser.length > 0 && selectedOrdersOfUser.length < item.orders.length + + return { + ...item, + selectedOrders: selectedOrdersOfUser, + ordersCount: item.orders.length, + totalValue, + selectedTotalValue, + isAllSelected: isUserAllSelected, + isSomeSelected: isUserSomeSelected, + } + }) + .sort((a, b) => a.name.localeCompare(b.name, "pt-BR")) + }, [orders, deselectedOrderIds]) + + const selectedGroupedUsers = useMemo(() => { + return groupedUsers.filter((u) => u.selectedOrders.length > 0) + }, [groupedUsers]) + const handleToggleSelectAll = () => { if (isAllSelected) { setDeselectedOrderIds(new Set(orders.map((o) => o.id))) @@ -216,6 +299,20 @@ export function OrdersReportModal({ }) } + const handleToggleUserGroup = (userGroup: (typeof groupedUsers)[number]) => { + setDeselectedOrderIds((prev) => { + const next = new Set(prev) + if (userGroup.isAllSelected) { + // Desmarcar todos os pedidos deste colaborador + userGroup.orders.forEach((o) => next.add(o.id)) + } else { + // Marcar todos os pedidos deste colaborador + userGroup.orders.forEach((o) => next.delete(o.id)) + } + return next + }) + } + const handleToggleUser = (userId: string) => { setSelectedUserIds((prev) => prev.includes(userId) ? prev.filter((id) => id !== userId) : [...prev, userId] @@ -237,6 +334,7 @@ export function OrdersReportModal({ setSelectedRestaurant(ALL_FILTER) setSelectedFilial(ALL_FILTER) setDeselectedOrderIds(new Set()) + setGroupByUser(true) setPage(1) } @@ -249,12 +347,19 @@ export function OrdersReportModal({ endDate.getTime() !== defaultEnd.getTime() // Paginação - const totalPages = Math.max(1, Math.ceil(orders.length / pageSize)) + const totalItems = groupByUser ? groupedUsers.length : orders.length + const totalPages = Math.max(1, Math.ceil(totalItems / pageSize)) + const paginatedOrders = useMemo(() => { const startIndex = (page - 1) * pageSize return orders.slice(startIndex, startIndex + pageSize) }, [orders, page, pageSize]) + const paginatedGroupedUsers = useMemo(() => { + const startIndex = (page - 1) * pageSize + return groupedUsers.slice(startIndex, startIndex + pageSize) + }, [groupedUsers, page, pageSize]) + const formatCurrency = (val: number) => { return new Intl.NumberFormat("pt-BR", { style: "currency", @@ -272,53 +377,128 @@ export function OrdersReportModal({ } try { - const detailedRows = ordersToExport.map((order) => { - const user = order.user - const matricula = user?.matricula ?? "" - const nome = `${user?.firstName ?? ""} ${user?.lastName ?? ""}`.trim() || "Não identificado" - const email = user?.email ?? "" - const setor = user?.setor ?? "" - const empresa = user?.filial?.empresa?.name ?? user?.enterprise ?? "" - const filial = user?.filial ? `${user.filial.name} (${user.filial.code})` : "" - const restaurante = order.restaurant?.name ?? "Restaurante excluído" - const prato = order.menuItem?.name ?? "" - const valor = order.unitPrice ?? order.menuItem?.price ?? 0 - - const statusLabel = - order.status === "DELIVERED" - ? "Entregue" - : order.status === "CONFIRMED" - ? "Confirmado" - : order.status === "PENDING" - ? "Pendente" - : order.status === "CANCELLED" - ? "Cancelado" - : order.status - - return { - "Data do Pedido": formatOrderDate(order.orderDate), - "Hora": order.orderTime ? format(new Date(order.orderTime), "HH:mm", { locale: ptBR }) : "", - "Matrícula": matricula, - "Colaborador": nome, - "E-mail": email, - "Setor": setor, - "Empresa": empresa, - "Filial": filial, - "Restaurante": restaurante, - "Prato": prato, - "Valor (R$)": valor, - "Status": statusLabel, - } - }) - const wb = XLSX.utils.book_new() - const wsDetailed = XLSX.utils.json_to_sheet(detailedRows) - XLSX.utils.book_append_sheet(wb, wsDetailed, "Pedidos") - const fileStart = format(startDate, "yyyy-MM-dd") const fileEnd = format(endDate, "yyyy-MM-dd") - XLSX.writeFile(wb, `pedidos_almoco_${fileStart}_a_${fileEnd}.xlsx`) - toast.success(`Planilha exportada com sucesso! (${ordersToExport.length} pedidos)`) + + if (groupByUser) { + // Agrupar por usuário somando os gastos totais do período + const userGroupsMap = new Map< + string, + { + matricula: string + nome: string + email: string + setor: string + empresa: string + filial: string + ordersCount: number + totalValue: number + } + >() + + for (const order of ordersToExport) { + const user = order.user + const key = user?.id ?? order.userId ?? "unknown" + const val = order.unitPrice ?? order.menuItem?.price ?? 0 + + const existing = userGroupsMap.get(key) + if (existing) { + existing.ordersCount += 1 + existing.totalValue += val + } else { + const matricula = user?.matricula ?? "" + const nome = + `${user?.firstName ?? ""} ${user?.lastName ?? ""}`.trim() || + user?.email || + "Não identificado" + const email = user?.email ?? "" + const setor = user?.setor ?? "" + const empresa = user?.filial?.empresa?.name ?? user?.enterprise ?? "" + const filial = user?.filial ? `${user.filial.name} (${user.filial.code})` : "" + + userGroupsMap.set(key, { + matricula, + nome, + email, + setor, + empresa, + filial, + ordersCount: 1, + totalValue: val, + }) + } + } + + const groupedRows = Array.from(userGroupsMap.values()) + .sort((a, b) => a.nome.localeCompare(b.nome, "pt-BR")) + .map((group) => ({ + "Matrícula": group.matricula, + "Colaborador": group.nome, + "E-mail": group.email, + "Setor": group.setor, + "Empresa": group.empresa, + "Filial": group.filial, + "Qtd. de Pedidos": group.ordersCount, + "Gasto Total (R$)": Number(group.totalValue.toFixed(2)), + })) + + const wsGrouped = XLSX.utils.json_to_sheet(groupedRows) + XLSX.utils.book_append_sheet(wb, wsGrouped, "Gastos por Usuário") + XLSX.writeFile(wb, `pedidos_almoco_agrupado_${fileStart}_a_${fileEnd}.xlsx`) + toast.success( + `Planilha agrupada exportada com sucesso! (${groupedRows.length} colaboradores, ${ordersToExport.length} pedidos)`, + ) + } else { + const detailedRows = ordersToExport.map((order) => { + const user = order.user + const matricula = user?.matricula ?? "" + const nome = + `${user?.firstName ?? ""} ${user?.lastName ?? ""}`.trim() || "Não identificado" + const email = user?.email ?? "" + const setor = user?.setor ?? "" + const empresa = user?.filial?.empresa?.name ?? user?.enterprise ?? "" + const filial = user?.filial ? `${user.filial.name} (${user.filial.code})` : "" + const restaurante = order.restaurant?.name ?? "Restaurante excluído" + const prato = order.menuItem?.name ?? "" + const valor = order.unitPrice ?? order.menuItem?.price ?? 0 + + const statusLabel = + order.status === "DELIVERED" + ? "Entregue" + : order.status === "CONFIRMED" + ? "Confirmado" + : order.status === "PENDING" + ? "Pendente" + : order.status === "CANCELLED" + ? "Cancelado" + : order.status + + return { + "Data do Pedido": formatOrderDate(order.orderDate), + "Hora": order.orderTime + ? format(new Date(order.orderTime), "HH:mm", { locale: ptBR }) + : "", + "Matrícula": matricula, + "Colaborador": nome, + "E-mail": email, + "Setor": setor, + "Empresa": empresa, + "Filial": filial, + "Restaurante": restaurante, + "Prato": prato, + "Valor (R$)": valor, + "Status": statusLabel, + } + }) + + const wb = XLSX.utils.book_new() + const wsDetailed = XLSX.utils.json_to_sheet(detailedRows) + XLSX.utils.book_append_sheet(wb, wsDetailed, "Pedidos") + + XLSX.writeFile(wb, `pedidos_almoco_${fileStart}_a_${fileEnd}.xlsx`) + toast.success(`Planilha exportada com sucesso! (${ordersToExport.length} pedidos)`) + } } catch (err: unknown) { console.error(err) toast.error("Ocorreu um erro ao exportar a planilha.") @@ -539,6 +719,95 @@ export function OrdersReportModal({ Ajuste as datas ou os filtros selecionados para visualizar os registros.

+ ) : groupByUser ? ( + + + + + + + Colaborador + Matrícula + Setor + Empresa / Filial + Qtd. Pedidos + Gasto Total + + + + {paginatedGroupedUsers.map((group) => { + return ( + + + handleToggleUserGroup(group)} + /> + + +
{group.name}
+
+ {group.email} +
+
+ + {group.matricula} + + + {group.setor !== "-" ? ( + + {group.setor} + + ) : ( + - + )} + + +
{group.empresa}
+ {group.filial && ( +
+ {group.filial} +
+ )} +
+ + + {group.ordersCount} {group.ordersCount === 1 ? "pedido" : "pedidos"} + + + + {formatCurrency( + group.selectedTotalValue > 0 + ? group.selectedTotalValue + : group.totalValue, + )} + +
+ ) + })} +
+
) : ( @@ -638,6 +907,11 @@ export function OrdersReportModal({ {selectedOrders.length} {" "} de {orders.length} pedidos + {groupByUser && ( + + ({selectedGroupedUsers.length} de {groupedUsers.length} colaboradores) + + )} {formatCurrency(selectedTotalValue)} @@ -648,8 +922,10 @@ export function OrdersReportModal({
- Mostrando {orders.length === 0 ? 0 : (page - 1) * pageSize + 1} a{" "} - {Math.min(page * pageSize, orders.length)} de {orders.length} pedidos + Mostrando {totalItems === 0 ? 0 : (page - 1) * pageSize + 1} a{" "} + {Math.min(page * pageSize, totalItems)} de {totalItems}{" "} + {groupByUser ? "colaboradores" : "pedidos"} + {groupByUser && orders.length > 0 && ` (${orders.length} pedidos)`}
@@ -701,6 +977,25 @@ export function OrdersReportModal({
)} + {/* Checkbox sutil para agrupar usuários (default marcado) */} +
+ { + setGroupByUser(!!checked) + setPage(1) + }} + className="h-3.5 w-3.5 rounded" + /> + +
+ @@ -711,7 +1006,7 @@ export function OrdersReportModal({ className="gap-1.5 text-xs h-8 font-medium" > - Exportar Excel ({selectedOrders.length}) + Exportar Excel ({groupByUser ? selectedGroupedUsers.length : selectedOrders.length})