diff --git a/src/app/(authenticated)/admin/food/_components/dre-report.tsx b/src/app/(authenticated)/admin/food/_components/dre-report.tsx index dfc624d0..20871826 100644 --- a/src/app/(authenticated)/admin/food/_components/dre-report.tsx +++ b/src/app/(authenticated)/admin/food/_components/dre-report.tsx @@ -9,12 +9,20 @@ import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Badge } from "@/components/ui/badge" -import { DatePicker } from "@/components/ui/date-picker" +import { DateRangeCalendar } from "@/components/forms/date-range-calendar" import { toast } from "sonner" -import { format } from "date-fns" +import { format, startOfMonth, endOfMonth } from "date-fns" import { ptBR } from "date-fns/locale" -import { Calculator, Download, FileText } from "lucide-react" +import { Calculator, Download, FileText, Eye, FileSpreadsheet } from "lucide-react" import * as XLSX from "xlsx" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" interface DREData { enterprise: string @@ -44,19 +52,29 @@ const BRANCH_ENTERPRISES = ['Box_Filial', 'Cristallux_Filial'] as const export default function DREReport({ selectedDate, setSelectedDate }: DREReportProps) { const [invoiceValue, setInvoiceValue] = useState("") - const [selectedPeriod, setSelectedPeriod] = useState<'month' | 'quarter' | 'year'>('month') - const [selectedYear, setSelectedYear] = useState(new Date().getFullYear()) + const [startDate, setStartDate] = useState( + startOfMonth(selectedDate) + ) + const [endDate, setEndDate] = useState( + endOfMonth(selectedDate) + ) const [rateioType, setRateioType] = useState('proportional') + const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false) + + // Calcular ano baseado na data inicial ou usar o ano atual + const selectedYear = startDate?.getFullYear() ?? new Date().getFullYear() // Buscar dados DRE por empresa/setor const dreQuery = api.foodOrder.getDREData.useQuery( { year: selectedYear, - period: selectedPeriod, - date: selectedPeriod === 'month' ? selectedDate : undefined, + period: "month", // Mantém compatibilidade + date: startDate, + startDate: startDate, + endDate: endDate, }, { - enabled: true, + enabled: !!(startDate && endDate), } ) @@ -148,7 +166,12 @@ export default function DREReport({ selectedDate, setSelectedDate }: DREReportPr return } - toast.success("Relatório DRE gerado com sucesso!") + if (filteredData.length === 0) { + toast.error("Nenhum dado encontrado para os filtros selecionados") + return + } + + setIsPreviewModalOpen(true) } const handleExportToExcel = () => { @@ -193,7 +216,13 @@ export default function DREReport({ selectedDate, setSelectedDate }: DREReportPr const wb = XLSX.utils.book_new() XLSX.utils.book_append_sheet(wb, ws, "DRE_Report") - const fileName = `dre_report_${selectedYear}_${selectedPeriod}_${format(new Date(), "yyyy-MM-dd")}.xlsx` + const isFullMonth = startDate && endDate && + startDate.getTime() === startOfMonth(startDate).getTime() && + endDate.getTime() === endOfMonth(startDate).getTime() + const periodLabel = isFullMonth + ? format(startDate ?? new Date(), "MM-yyyy", { locale: ptBR }) + : `${format(startDate ?? new Date(), "dd-MM-yyyy", { locale: ptBR })}_${format(endDate ?? new Date(), "dd-MM-yyyy", { locale: ptBR })}` + const fileName = `dre_report_${periodLabel}_${format(new Date(), "yyyy-MM-dd")}.xlsx` XLSX.writeFile(wb, fileName) toast.success("Relatório DRE exportado com sucesso!") @@ -254,58 +283,14 @@ export default function DREReport({ selectedDate, setSelectedDate }: DREReportPr
- {/* Primeira linha: Período, Ano, Data */} -
-
- - -
- -
- - -
- {selectedPeriod === 'month' && ( -
-
- { - if (date) setSelectedDate(date) - }} - /> -
- )} -
+ {/* Seleção de Período */} + {/* Segunda linha: Valor da Nota, Tipo de Rateio */}
@@ -341,8 +326,13 @@ export default function DREReport({ selectedDate, setSelectedDate }: DREReportPr
-
@@ -381,9 +371,15 @@ export default function DREReport({ selectedDate, setSelectedDate }: DREReportPr Resultado DRE por Empresa e Setor - {selectedPeriod === 'month' && `Dados do mês ${format(selectedDate, "MM/yyyy", { locale: ptBR })}`} - {selectedPeriod === 'quarter' && `Dados do trimestre ${Math.ceil((selectedDate.getMonth() + 1) / 3)}/${selectedYear}`} - {selectedPeriod === 'year' && `Dados do ano ${selectedYear}`} + {startDate && endDate && ( + <> + {startDate.getTime() === startOfMonth(startDate).getTime() && + endDate.getTime() === endOfMonth(startDate).getTime() + ? `Dados do mês ${format(startDate, "MMMM 'de' yyyy", { locale: ptBR })}` + : `Dados do período de ${format(startDate, "dd/MM/yyyy", { locale: ptBR })} até ${format(endDate, "dd/MM/yyyy", { locale: ptBR })}` + } + + )} {rateioType !== 'proportional' && ( Rateio aplicado: { @@ -449,6 +445,139 @@ export default function DREReport({ selectedDate, setSelectedDate }: DREReportPr )} + + {/* Modal de Pré-visualização do Relatório */} + + + + + + Pré-visualização do Relatório DRE + + + {startDate && endDate && ( + <> + {startDate.getTime() === startOfMonth(startDate).getTime() && + endDate.getTime() === endOfMonth(startDate).getTime() + ? `Período: ${format(startDate, "MMMM 'de' yyyy", { locale: ptBR })}` + : `Período: ${format(startDate, "dd/MM/yyyy", { locale: ptBR })} até ${format(endDate, "dd/MM/yyyy", { locale: ptBR })}` + } + {invoiceValue && ( + + Valor da Nota Fiscal: R$ {parseFloat(invoiceValue).toFixed(2)} | + Tipo de Rateio: { + rateioType === 'proportional' ? 'Proporcional' : + rateioType === 'headquarters' ? 'Matriz' : + 'Filial' + } + + )} + + )} + + + +
+ {/* Resumo */} + {filteredData.length > 0 && ( +
+ + +
{totalOrders}
+

Total de Pedidos

+
+
+ + +
R$ {totalValue.toFixed(2)}
+

Valor Total

+
+
+ + +
R$ {totalRateio.toFixed(2)}
+

Total Rateio

+
+
+
+ )} + + {/* Tabela de Resultados */} + {filteredData.length > 0 ? ( +
+
+ + + + Empresa + Setor + Pedidos + Valor (R$) + Representatividade (%) + Rateio Centro Custo (R$) + + + + {filteredData.map((item, index) => ( + + + {item.enterprise} + + {item.sector ?? "Não informado"} + {item.totalOrders} + R$ {item.totalValue.toFixed(2)} + {item.representativeness.toFixed(2)}% + + R$ {item.costCenterRateio?.toFixed(2) ?? "0.00"} + + + ))} + +
+
+ + {/* Totais */} +
+
+ Total Geral: + {totalOrders} pedidos + R$ {totalValue.toFixed(2)} + 100.00% + R$ {totalRateio.toFixed(2)} +
+
+
+ ) : ( +
+ +

+ Nenhum dado encontrado para os filtros selecionados +

+
+ )} +
+ + + + + +
+
) } diff --git a/src/components/forms/date-range-calendar.tsx b/src/components/forms/date-range-calendar.tsx new file mode 100644 index 00000000..91c10ae7 --- /dev/null +++ b/src/components/forms/date-range-calendar.tsx @@ -0,0 +1,176 @@ +"use client" + +import { useState, useMemo, useEffect } from "react" +import { format } from "date-fns" +import { ptBR } from "date-fns/locale" +import { Calendar as CalendarIcon, X } from "lucide-react" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { Label } from "@/components/ui/label" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import type { DateRange as DateRangeType } from "react-day-picker" + +interface DateRangeCalendarProps { + startDate?: Date + endDate?: Date + onStartDateChange: (date: Date | undefined) => void + onEndDateChange: (date: Date | undefined) => void + className?: string + label?: string + placeholder?: string +} + +export function DateRangeCalendar({ + startDate, + endDate, + onStartDateChange, + onEndDateChange, + className, + label = "Período", + placeholder = "Selecione o período", +}: DateRangeCalendarProps) { + const [isOpen, setIsOpen] = useState(false) + const [calendarMonth, setCalendarMonth] = useState(() => startDate ?? new Date()) + + // Criar objeto DateRange para o Calendar + const dateRange: DateRangeType | undefined = useMemo(() => { + if (startDate && endDate) { + return { + from: startDate, + to: endDate, + } + } + if (startDate) { + return { + from: startDate, + to: undefined, + } + } + return undefined + }, [startDate, endDate]) + + // Sincronizar o mês do calendário quando startDate mudar + useEffect(() => { + if (startDate) { + setCalendarMonth(startDate) + } + }, [startDate]) + + // Quando o popover abrir, resetar o mês para a primeira data selecionada + const handleOpenChange = (open: boolean) => { + // Não permitir fechar se apenas a primeira data está selecionada + if (!open && startDate && !endDate) { + return + } + setIsOpen(open) + if (open) { + // Quando abrir, mostrar o calendário a partir da primeira data selecionada + setCalendarMonth(startDate ?? new Date()) + } + } + + const handleSelect = (range: DateRangeType | undefined) => { + if (!range) { + onStartDateChange(undefined) + onEndDateChange(undefined) + return + } + + // Atualizar data inicial + onStartDateChange(range.from) + + // Atualizar data final (pode ser undefined se ainda está selecionando) + onEndDateChange(range.to) + + // Fechar o popover quando o período estiver completo + if (range.from && range.to) { + setIsOpen(false) + } + } + + const clearDates = () => { + onStartDateChange(undefined) + onEndDateChange(undefined) + } + + const formatDisplay = () => { + if (startDate && endDate) { + return `${format(startDate, "dd/MM/yyyy", { locale: ptBR })} - ${format(endDate, "dd/MM/yyyy", { locale: ptBR })}` + } + if (startDate) { + return `${format(startDate, "dd/MM/yyyy", { locale: ptBR })} - ...` + } + return placeholder + } + + return ( +
+ {label && } +
+ + + + + { + // Prevenir fechamento ao clicar fora se apenas a primeira data está selecionada + if (startDate && !endDate) { + e.preventDefault() + } + }} + onEscapeKeyDown={(e) => { + // Prevenir fechamento com ESC se apenas a primeira data está selecionada + if (startDate && !endDate) { + e.preventDefault() + } + }} + > + {startDate && !endDate && ( +
+ Aguardando seleção da data final... +
+ )} + +
+
+ + {(startDate ?? endDate) && ( + + )} +
+
+ ) +} + diff --git a/src/components/forms/period-selector.tsx b/src/components/forms/period-selector.tsx new file mode 100644 index 00000000..a189c6eb --- /dev/null +++ b/src/components/forms/period-selector.tsx @@ -0,0 +1,288 @@ +"use client" + +import { useState, useEffect, useMemo, useCallback } from "react" +import { format, startOfMonth, endOfMonth } from "date-fns" +import { ptBR } from "date-fns/locale" +import { Calendar as CalendarIcon } from "lucide-react" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { Label } from "@/components/ui/label" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" + +export type PeriodMode = "month" | "range" + +interface PeriodSelectorProps { + mode?: PeriodMode + startDate?: Date + endDate?: Date + onModeChange?: (mode: PeriodMode) => void + onStartDateChange: (date: Date | undefined) => void + onEndDateChange: (date: Date | undefined) => void + className?: string + label?: string +} + +export function PeriodSelector({ + mode: externalMode, + startDate, + endDate, + onModeChange, + onStartDateChange, + onEndDateChange, + className, + label = "Período", +}: PeriodSelectorProps) { + const [internalMode, setInternalMode] = useState(externalMode ?? "month") + const [isStartOpen, setIsStartOpen] = useState(false) + const [isEndOpen, setIsEndOpen] = useState(false) + const [selectedMonth, setSelectedMonth] = useState( + startDate?.getMonth() ?? new Date().getMonth() + ) + const [selectedYear, setSelectedYear] = useState( + startDate?.getFullYear() ?? new Date().getFullYear() + ) + + const currentMode = externalMode ?? internalMode + + // Quando muda o mês/ano no modo month, atualizar as datas + useEffect(() => { + if (currentMode === "month") { + const monthStart = startOfMonth(new Date(selectedYear, selectedMonth, 1)) + const monthEnd = endOfMonth(new Date(selectedYear, selectedMonth, 1)) + + // Só atualizar se as datas realmente mudaram para evitar loops + if (!startDate || startDate.getTime() !== monthStart.getTime()) { + onStartDateChange(monthStart) + } + if (!endDate || endDate.getTime() !== monthEnd.getTime()) { + onEndDateChange(monthEnd) + } + } + }, [selectedMonth, selectedYear, currentMode, onStartDateChange, onEndDateChange, startDate, endDate]) + + // Sincronizar mês/ano quando startDate muda externamente (apenas no modo month) + useEffect(() => { + if (startDate && currentMode === "month") { + const month = startDate.getMonth() + const year = startDate.getFullYear() + + if (month !== selectedMonth || year !== selectedYear) { + setSelectedMonth(month) + setSelectedYear(year) + } + } + }, [startDate, currentMode, selectedMonth, selectedYear]) + + const handleModeChange = useCallback((newMode: PeriodMode) => { + if (onModeChange) { + onModeChange(newMode) + } else { + setInternalMode(newMode) + } + + // Ao mudar para modo range, manter as datas atuais se existirem + if (newMode === "range" && !startDate && !endDate) { + // Se não há datas, usar o mês atual como padrão + const now = new Date() + onStartDateChange(startOfMonth(now)) + onEndDateChange(endOfMonth(now)) + } + }, [onModeChange, startDate, endDate, onStartDateChange, onEndDateChange]) + + const handleMonthChange = useCallback((month: string) => { + setSelectedMonth(parseInt(month)) + }, []) + + const handleYearChange = useCallback((year: string) => { + setSelectedYear(parseInt(year)) + }, []) + + // Gerar lista de anos (últimos 5 anos + próximos 2) + const years = useMemo(() => { + const currentYear = new Date().getFullYear() + return Array.from({ length: 8 }, (_, i) => currentYear - 5 + i) + }, []) + + // Meses do ano + const months = [ + { value: "0", label: "Janeiro" }, + { value: "1", label: "Fevereiro" }, + { value: "2", label: "Março" }, + { value: "3", label: "Abril" }, + { value: "4", label: "Maio" }, + { value: "5", label: "Junho" }, + { value: "6", label: "Julho" }, + { value: "7", label: "Agosto" }, + { value: "8", label: "Setembro" }, + { value: "9", label: "Outubro" }, + { value: "10", label: "Novembro" }, + { value: "11", label: "Dezembro" }, + ] + + const formatPeriodDisplay = () => { + if (!startDate || !endDate) return "Selecione o período" + + if (currentMode === "month") { + return format(startDate, "MMMM 'de' yyyy", { locale: ptBR }) + } + + return `${format(startDate, "dd/MM/yyyy", { locale: ptBR })} - ${format(endDate, "dd/MM/yyyy", { locale: ptBR })}` + } + + return ( +
+ + + {/* Seletor de modo */} +
+ + + {/* Conteúdo baseado no modo */} + {currentMode === "month" ? ( +
+
+ + +
+ +
+ + +
+
+ ) : ( +
+ + + + + + { + if (date) { + onStartDateChange(date) + setIsStartOpen(false) + } + }} + initialFocus + /> + + + + + + + + + { + if (date) { + onEndDateChange(date) + setIsEndOpen(false) + } + }} + disabled={(date) => { + if (startDate) { + return date < startDate + } + return false + }} + initialFocus + /> + + +
+ )} + + {/* Exibição do período selecionado */} + {(startDate && endDate) && ( +
+ Período selecionado: {formatPeriodDisplay()} +
+ )} +
+
+ ) +} + diff --git a/src/server/api/routers/food-order.ts b/src/server/api/routers/food-order.ts index d1f4a993..e6e64386 100644 --- a/src/server/api/routers/food-order.ts +++ b/src/server/api/routers/food-order.ts @@ -916,6 +916,8 @@ export const foodOrderRouter = createTRPCRouter({ year: z.number(), period: z.enum(["month", "quarter", "year"]), date: z.date().optional(), + startDate: z.date().optional(), + endDate: z.date().optional(), }), ) .query(async ({ ctx, input }): Promise<{ @@ -924,13 +926,33 @@ export const foodOrderRouter = createTRPCRouter({ totalOrders: number totalValue: number }[]> => { - const { year, period, date } = input + const { year, period, date, startDate: inputStartDate, endDate: inputEndDate } = input // Definir o período de busca baseado no tipo selecionado let startDate: Date let endDate: Date - if (period === "year") { + // Se startDate e endDate foram fornecidos diretamente, usar esses valores + if (inputStartDate && inputEndDate) { + startDate = new Date(Date.UTC( + inputStartDate.getFullYear(), + inputStartDate.getMonth(), + inputStartDate.getDate(), + 0, + 0, + 0, + 0 + )) + endDate = new Date(Date.UTC( + inputEndDate.getFullYear(), + inputEndDate.getMonth(), + inputEndDate.getDate(), + 23, + 59, + 59, + 999 + )) + } else if (period === "year") { startDate = new Date(Date.UTC(year, 0, 1)) // 1 de janeiro endDate = new Date(Date.UTC(year, 11, 31, 23, 59, 59, 999)) // 31 de dezembro } else if (period === "quarter" && date) {