From b567a4580311f24984079bf352cd9f1190700f1a Mon Sep 17 00:00:00 2001 From: Welton Moura Date: Mon, 27 Jul 2026 14:13:56 -0300 Subject: [PATCH] feat: estoque negativo, UTC-3, dd/mm/yyyy, estorno de pagamento --- AGENTS.md | 15 ++++--- prisma/schema.prisma | 1 + src/controllers/order.controller.ts | 10 +++++ src/controllers/report.controller.ts | 13 +++++- src/lib/timezone.ts | 62 ++++++++++++++++++++++++++ src/routes/order.routes.ts | 3 ++ src/schemas/product.schema.ts | 4 +- src/services/order.service.ts | 62 +++++++++++++++++--------- src/services/report.service.ts | 14 +++--- web/src/utils.ts | 22 ++++++++++ web/src/views/checkout.ts | 65 +++++++++++++++++++--------- web/src/views/dashboard.ts | 24 ++++++---- web/src/views/menu.ts | 4 +- web/src/views/orders.ts | 15 ++++--- 14 files changed, 239 insertions(+), 75 deletions(-) create mode 100644 src/lib/timezone.ts diff --git a/AGENTS.md b/AGENTS.md index af17fa0..cc452e7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,7 +34,8 @@ app_ces/ ├── src/ # Backend API │ ├── server.ts # Entrypoint Fastify, porta 3333 │ ├── lib/ -│ │ └── prisma.ts # Instância PrismaClient +│ │ ├── prisma.ts # Instância PrismaClient +│ │ └── timezone.ts # Helpers UTC-3 (dateStart, dateEnd, dateTime, nowUTC3) │ ├── middlewares/ │ │ └── auth.middleware.ts # authenticate + requireRoles │ ├── controllers/ @@ -70,7 +71,7 @@ app_ces/ └── src/ ├── main.ts # Router SPA + navegação ├── api.ts # Cliente HTTP centralizado - ├── utils.ts # renderNavbar, toast, helpers + ├── utils.ts # renderNavbar, toast, helpers, toDisplayDate, parseDisplayDate ├── style.css # Dark theme + glassmorphism └── views/ ├── login.ts @@ -107,7 +108,7 @@ app_ces/ - Relations: `produto` (N:1 Produto), `comanda` (N:1 Comanda), `usuario` (N:1 Usuario) **Pagamento** -- `id` (UUID), `comandaId` (FK), `valor`, `formaPagamento` (DINHEIRO|CARTAO_CREDITO|CARTAO_DEBITO|PIX), `observacao?`, `criadoEm` +- `id` (UUID), `comandaId` (FK), `valor`, `formaPagamento` (DINHEIRO|CARTAO_CREDITO|CARTAO_DEBITO|PIX), `observacao?`, `estornado` (boolean, default false), `criadoEm` ## Rotas da API @@ -148,6 +149,7 @@ app_ces/ | PATCH | `/comandas/:id` | GARCOM/CAIXA/ADMIN | | POST | `/comandas/:id/pagar` | CAIXA/ADMIN | | POST | `/comandas/:id/pagamentos` | CAIXA/ADMIN | +| PATCH | `/pagamentos/:id/estornar` | CAIXA/ADMIN | | PATCH | `/comandas/itens/:id/observacao` | GARCOM/CAIXA/ADMIN | | DELETE | `/comandas/itens/:id` | CAIXA/ADMIN | @@ -187,7 +189,7 @@ app_ces/ ### Fluxo de Comanda 1. Criar comanda (identificador = mesa/código) - **registra usuário logado** 2. Adicionar itens (baixa estoque automaticamente, atualiza total) - **registra usuário logado** -3. Itens aparecem no painel de cozinha/bar com status `PENDENTE` +3. Itens aparecem no painel de cozinha/bar com status `PENDENTE` (apenas últimos 12h) 4. Cozinha/bar avança: `PENDENTE → PREPARANDO → PRONTO → ENTREGUE` 5. Caixa fecha com pagamento (integral ou parcial) 6. Status da comanda: `ABERTA → PAGA` @@ -197,17 +199,19 @@ app_ces/ - **Parcial**: `POST /comandas/:id/pagamentos` - registra pagamento parcial, pode selecionar itens específicos - **Cálculo do total final**: `total - desconto + acrescimo + (taxaServico ? 10% : 0)` - Formas: `DINHEIRO`, `CARTAO_CREDITO`, `CARTAO_DEBITO`, `PIX` +- **Estorno**: `PATCH /pagamentos/:id/estornar` - marca pagamento como estornado, subtrai valor de `comanda.valorPago`, se comanda era `PAGA` volta para `FECHADA`. Pagamento continua visível com indicador visual (opacity + line-through + badge "ESTORNADO") ### Estoque - Decrementa ao adicionar item na comanda - Incrementa ao remover item da comanda -- Validação de estoque insuficiente no backend +- **Permite estoque negativo** (sem validação de insuficiência) ### Frontend - Rotas SPA manuais via `window.history.pushState` - Auto-refresh no painel de cozinha a cada 10 segundos - Notificações sonoras via Web Audio API para novos pedidos - Navbar dinâmica baseada no role do usuário logado +- Filtros de data em formato `dd/mm/yyyy` (inputs `type="text"`, conversão via `parseDisplayDate`) ## Comandos Úteis @@ -232,6 +236,7 @@ cd web && npm run build # Build de produção - CORS habilitado para qualquer origem (`origin: '*'`) - JWT secret definido em `.env` (fallback: `fallback_secret_change_me`) - Error handler global trata `ZodError` (400) e erros HTTP +- **Fuso horário UTC-3** configurado em `src/lib/timezone.ts` - todas as datas no backend e frontend usam offset UTC-3 - Frontend não usa framework UI - renderiza HTML via template literals - Utiliza `event delegation` para botões dinâmicos no painel - Cleanup de listeners ao navegar entre views diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 724109d..d8172a1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -83,5 +83,6 @@ model Pagamento { valor Float formaPagamento String // DINHEIRO, CARTAO_CREDITO, CARTAO_DEBITO, PIX observacao String? + estornado Boolean @default(false) criadoEm DateTime @default(now()) } diff --git a/src/controllers/order.controller.ts b/src/controllers/order.controller.ts index 56a5e8f..ea82d73 100644 --- a/src/controllers/order.controller.ts +++ b/src/controllers/order.controller.ts @@ -133,3 +133,13 @@ export async function deleteItemFromOrderController(request: FastifyRequest, rep return reply.status(400).send({ error: error.message }); } } + +export async function reversePaymentController(request: FastifyRequest, reply: FastifyReply) { + const { id } = z.object({ id: z.string().uuid() }).parse(request.params); + try { + const pagamento = await orderService.reversePayment(id); + return reply.send(pagamento); + } catch (error: any) { + return reply.status(400).send({ error: error.message }); + } +} diff --git a/src/controllers/report.controller.ts b/src/controllers/report.controller.ts index 30a5351..b42145a 100644 --- a/src/controllers/report.controller.ts +++ b/src/controllers/report.controller.ts @@ -1,16 +1,25 @@ import { FastifyRequest, FastifyReply } from 'fastify'; import * as reportService from '../services/report.service'; import { z } from 'zod'; +import { dateTime, nowUTC3, todayStrUTC3 } from '../lib/timezone'; export async function salesReportController(request: FastifyRequest, reply: FastifyReply) { const querySchema = z.object({ inicio: z.string().optional(), fim: z.string().optional(), + horaInicio: z.string().optional(), + horaFim: z.string().optional(), }); const query = querySchema.parse(request.query); - const inicio = query.inicio ? new Date(query.inicio) : new Date(new Date().setDate(new Date().getDate() - 30)); - const fim = query.fim ? new Date(query.fim + 'T23:59:59') : new Date(); + const today = todayStrUTC3(); + const inicioStr = query.inicio || today; + const horaInicio = query.horaInicio || '00:00'; + const inicio = dateTime(inicioStr, horaInicio); + + const fimStr = query.fim || today; + const horaFim = query.horaFim || '23:59'; + const fim = dateTime(fimStr, horaFim, 59); const report = await reportService.getSalesReport(inicio, fim); return reply.send(report); diff --git a/src/lib/timezone.ts b/src/lib/timezone.ts new file mode 100644 index 0000000..74bebe9 --- /dev/null +++ b/src/lib/timezone.ts @@ -0,0 +1,62 @@ +const OFFSET_HOURS = -3; + +/** Componentes de data/hora em UTC-3 */ +export interface UTC3 { + year: number; + month: number; // 0-indexed + date: number; + hours: number; + minutes: number; + seconds: number; +} + +/** Retorna componentes de agora em UTC-3 */ +export function nowUTC3(): UTC3 { + const d = new Date(Date.now() + OFFSET_HOURS * 60 * 60 * 1000); + return { + year: d.getUTCFullYear(), + month: d.getUTCMonth(), + date: d.getUTCDate(), + hours: d.getUTCHours(), + minutes: d.getUTCMinutes(), + seconds: d.getUTCSeconds(), + }; +} + +/** Cria Date cuja representação UTC = meia-noite UTC-3 do dateStr (YYYY-MM-DD) */ +export function dateStart(dateStr: string): Date { + const [y, m, d] = dateStr.split('-').map(Number); + return new Date(Date.UTC(y, m - 1, d, 0 - OFFSET_HOURS, 0, 0, 0)); +} + +/** Cria Date cuja representação UTC = 23:59:59.999 UTC-3 do dateStr */ +export function dateEnd(dateStr: string): Date { + const [y, m, d] = dateStr.split('-').map(Number); + return new Date(Date.UTC(y, m - 1, d, 23 - OFFSET_HOURS, 59, 59, 999)); +} + +/** Date + hora em UTC-3. dateStr = YYYY-MM-DD, timeStr = HH:mm */ +export function dateTime(dateStr: string, timeStr: string, seconds = 0): Date { + const [y, m, d] = dateStr.split('-').map(Number); + const [hh, mm] = timeStr.split(':').map(Number); + return new Date(Date.UTC(y, m - 1, d, hh - OFFSET_HOURS, mm, seconds, 0)); +} + +/** Converte Date (UTC no banco) para UTC-3 para exibição */ +export function toUTC3(d: Date): Date { + return new Date(d.getTime() + OFFSET_HOURS * 60 * 60 * 1000); +} + +/** YYYY-MM-DD em UTC-3 */ +export function todayStrUTC3(): string { + const c = nowUTC3(); + const pad = (n: number) => String(n).padStart(2, '0'); + return `${c.year}-${pad(c.month + 1)}-${pad(c.date)}`; +} + +/** YYYY-MM-DD do primeiro dia do mês atual em UTC-3 */ +export function firstDayOfMonthUTC3(): string { + const c = nowUTC3(); + const pad = (n: number) => String(n).padStart(2, '0'); + return `${c.year}-${pad(c.month + 1)}-01`; +} diff --git a/src/routes/order.routes.ts b/src/routes/order.routes.ts index e3173bc..f63c619 100644 --- a/src/routes/order.routes.ts +++ b/src/routes/order.routes.ts @@ -27,4 +27,7 @@ export async function orderRoutes(app: FastifyInstance) { // Excluir item de comanda (apenas CAIXA e ADMIN) app.delete('/comandas/itens/:id', { preHandler: [requireRoles(['CAIXA', 'ADMIN'])] }, orderController.deleteItemFromOrderController); + + // Estornar pagamento (Caixa, Admin) + app.patch('/pagamentos/:id/estornar', { preHandler: [requireRoles(['CAIXA', 'ADMIN'])] }, orderController.reversePaymentController); } diff --git a/src/schemas/product.schema.ts b/src/schemas/product.schema.ts index 864aaf5..cb76d52 100644 --- a/src/schemas/product.schema.ts +++ b/src/schemas/product.schema.ts @@ -6,8 +6,8 @@ export const productSchema = z.object({ fotoUrl: z.string().url().optional(), preco: z.number().positive(), custo: z.number().positive().optional(), - estoqueAtual: z.number().int().nonnegative().default(0), - estoqueMinimo: z.number().int().nonnegative().default(0), + estoqueAtual: z.number().int().default(0), + estoqueMinimo: z.number().int().default(0), itemCozinha: z.boolean(), ativo: z.boolean().default(true), categoriaId: z.string().uuid(), diff --git a/src/services/order.service.ts b/src/services/order.service.ts index 3b01efb..f80d63d 100644 --- a/src/services/order.service.ts +++ b/src/services/order.service.ts @@ -1,5 +1,6 @@ import { prisma } from '../lib/prisma'; import { CreateOrderInput, AddItemOrderInput, UpdateItemStatusInput, UpdateItemObservacaoInput, UpdateOrderInput, PayOrderInput, PartialPaymentInput } from '../schemas/order.schema'; +import { dateStart, dateEnd, nowUTC3 } from '../lib/timezone'; export interface ListOrdersFilter { statuses?: string[]; @@ -17,14 +18,8 @@ export async function listPayments(filter?: ListPaymentsFilter) { if (filter?.dataInicio || filter?.dataFim) { where.criadoEm = {}; - if (filter.dataInicio) { - const [y, m, d] = filter.dataInicio.split('-').map(Number); - where.criadoEm.gte = new Date(y, m - 1, d, 0, 0, 0, 0); - } - if (filter.dataFim) { - const [y, m, d] = filter.dataFim.split('-').map(Number); - where.criadoEm.lte = new Date(y, m - 1, d, 23, 59, 59, 999); - } + if (filter.dataInicio) where.criadoEm.gte = dateStart(filter.dataInicio); + if (filter.dataFim) where.criadoEm.lte = dateEnd(filter.dataFim); } return prisma.pagamento.findMany({ @@ -52,12 +47,8 @@ export async function listOrders(filter?: ListOrdersFilter) { if (filter?.dataInicio || filter?.dataFim) { where.criadoEm = {}; - if (filter.dataInicio) where.criadoEm.gte = new Date(filter.dataInicio); - if (filter.dataFim) { - const end = new Date(filter.dataFim); - end.setHours(23, 59, 59, 999); - where.criadoEm.lte = end; - } + if (filter.dataInicio) where.criadoEm.gte = dateStart(filter.dataInicio); + if (filter.dataFim) where.criadoEm.lte = dateEnd(filter.dataFim); } return prisma.comanda.findMany({ @@ -101,10 +92,6 @@ export async function addItemToOrder(comandaId: string, data: AddItemOrderInput, throw new Error('Produto não encontrado'); } - if (produto.estoqueAtual < data.quantidade) { - throw new Error(`Estoque insuficiente. Disponível: ${produto.estoqueAtual}`); - } - const precoTotalItem = produto.preco * data.quantidade; const [, itemPedido] = await prisma.$transaction([ @@ -134,11 +121,15 @@ export async function addItemToOrder(comandaId: string, data: AddItemOrderInput, export async function listKitchenBarItems(itemCozinha?: boolean, status?: string) { const filter: any = {}; - + + const now = nowUTC3(); + const dozeHorasAtrasMs = Date.UTC(now.year, now.month, now.date, now.hours - 12, now.minutes, now.seconds, 0); + filter.criadoEm = { gte: new Date(dozeHorasAtrasMs) }; + if (status) { filter.status = status; } - + if (typeof itemCozinha === 'boolean') { filter.produto = { itemCozinha, @@ -396,3 +387,34 @@ export async function deleteItemFromOrder(itemId: string) { }, }); } + +export async function reversePayment(pagamentoId: string) { + const pagamento = await prisma.pagamento.findUnique({ + where: { id: pagamentoId }, + include: { comanda: true }, + }); + + if (!pagamento) throw new Error('Pagamento não encontrado'); + if (pagamento.estornado) throw new Error('Pagamento já foi estornado'); + + const novoValorPago = Math.max(pagamento.comanda.valorPago - pagamento.valor, 0); + + const statusComanda = pagamento.comanda.status === 'PAGA' ? 'FECHADA' : pagamento.comanda.status; + + const [, pagamentoEstornado] = await prisma.$transaction([ + prisma.pagamento.update({ + where: { id: pagamentoId }, + data: { estornado: true }, + }), + prisma.comanda.update({ + where: { id: pagamento.comandaId }, + data: { + valorPago: novoValorPago, + status: statusComanda, + ...(pagamento.comanda.status === 'PAGA' ? { fechadoEm: null, formaPagamento: null } : {}), + }, + }), + ]); + + return pagamentoEstornado; +} diff --git a/src/services/report.service.ts b/src/services/report.service.ts index 17dfb0b..b4e20f6 100644 --- a/src/services/report.service.ts +++ b/src/services/report.service.ts @@ -1,4 +1,5 @@ import { prisma } from '../lib/prisma'; +import { nowUTC3, toUTC3 } from '../lib/timezone'; export async function getSalesReport(inicio: Date, fim: Date) { const comandas = await prisma.comanda.findMany({ @@ -39,7 +40,7 @@ export async function getSalesReport(inicio: Date, fim: Date) { // Vendas por dia const vendasPorDia: Record = {}; for (const c of comandas) { - const dia = c.fechadoEm!.toISOString().split('T')[0]; + const dia = toUTC3(c.fechadoEm!).toISOString().split('T')[0]; if (!vendasPorDia[dia]) vendasPorDia[dia] = { vendas: 0, comandas: 0 }; vendasPorDia[dia].vendas += c.total; vendasPorDia[dia].comandas += 1; @@ -80,12 +81,11 @@ export async function getSalesReport(inicio: Date, fim: Date) { } export async function getDashboardData() { - const now = new Date(); - const inicioDia = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - const inicioSemana = new Date(now); - inicioSemana.setDate(now.getDate() - now.getDay()); - inicioSemana.setHours(0, 0, 0, 0); - const inicioMes = new Date(now.getFullYear(), now.getMonth(), 1); + const now = nowUTC3(); + const inicioDia = new Date(Date.UTC(now.year, now.month, now.date, 0, 0, 0, 0)); + const diaSemana = new Date(Date.UTC(now.year, now.month, now.date)).getUTCDay(); + const inicioSemana = new Date(Date.UTC(now.year, now.month, now.date - diaSemana, 0, 0, 0, 0)); + const inicioMes = new Date(Date.UTC(now.year, now.month, 1, 0, 0, 0, 0)); const [comandasHoje, comandasSemana, comandasMes, totalProdutos, produtosEstoqueBaixo, comandasAbertas] = await Promise.all([ prisma.comanda.findMany({ diff --git a/web/src/utils.ts b/web/src/utils.ts index 154361d..f841c25 100644 --- a/web/src/utils.ts +++ b/web/src/utils.ts @@ -21,6 +21,28 @@ export function getUser(): { id: string; nome: string; email: string; role: stri return raw ? JSON.parse(raw) : null; } +/** Retorna YYYY-MM-DD de hoje em UTC-3 */ +export function todayStr(): string { + const d = new Date(Date.now() - 3 * 60 * 60 * 1000); + const y = d.getUTCFullYear(); + const m = String(d.getUTCMonth() + 1).padStart(2, '0'); + const day = String(d.getUTCDate()).padStart(2, '0'); + return `${y}-${m}-${day}`; +} + +/** Formata Date → dd/mm/yyyy para exibição */ +export function toDisplayDate(iso: string): string { + const [y, m, d] = iso.split('-'); + return `${d}/${m}/${y}`; +} + +/** Converte dd/mm/yyyy → YYYY-MM-DD para a API */ +export function parseDisplayDate(display: string): string { + const [d, m, y] = display.split('/'); + if (!d || !m || !y) return ''; + return `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`; +} + /** Formata valor em R$ */ export function formatBRL(value: number): string { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); diff --git a/web/src/views/checkout.ts b/web/src/views/checkout.ts index 98aece8..3457637 100644 --- a/web/src/views/checkout.ts +++ b/web/src/views/checkout.ts @@ -1,5 +1,5 @@ import { api } from '../api'; -import { renderNavbar, formatBRL } from '../utils'; +import { renderNavbar, formatBRL, toDisplayDate, parseDisplayDate, todayStr, getUser, toast } from '../utils'; const FORMAS_PAGAMENTO_LABEL: Record = { DINHEIRO: '💵 Dinheiro', @@ -8,18 +8,12 @@ const FORMAS_PAGAMENTO_LABEL: Record = { PIX: '📱 PIX', }; -function toLocalDateStr(d: Date): string { - const y = d.getFullYear(); - const m = String(d.getMonth() + 1).padStart(2, '0'); - const day = String(d.getDate()).padStart(2, '0'); - return `${y}-${m}-${day}`; -} - function getDefaultDateRange(): { inicio: string; fim: string } { - const now = new Date(); - const fim = toLocalDateStr(now); - const inicio = toLocalDateStr(new Date(now.getTime() - 12 * 60 * 60 * 1000)); - return { inicio, fim }; + const hoje = todayStr(); + const ontemMs = new Date(hoje + 'T12:00:00Z').getTime() - 12 * 60 * 60 * 1000; + const ontem = new Date(ontemMs); + const ontemStr = `${ontem.getUTCFullYear()}-${String(ontem.getUTCMonth() + 1).padStart(2, '0')}-${String(ontem.getUTCDate()).padStart(2, '0')}`; + return { inicio: toDisplayDate(ontemStr), fim: toDisplayDate(hoje) }; } export async function renderCheckout(container: HTMLElement) { @@ -37,11 +31,11 @@ export async function renderCheckout(container: HTMLElement) {
- +
- +
@@ -67,8 +61,8 @@ export async function renderCheckout(container: HTMLElement) { } async function loadPagamentos() { - const dataInicio = (document.getElementById('checkout-data-inicio') as HTMLInputElement).value; - const dataFim = (document.getElementById('checkout-data-fim') as HTMLInputElement).value; + const dataInicio = parseDisplayDate((document.getElementById('checkout-data-inicio') as HTMLInputElement).value); + const dataFim = parseDisplayDate((document.getElementById('checkout-data-fim') as HTMLInputElement).value); const lista = document.getElementById('checkout-pagamentos-lista')!; const totalEl = document.getElementById('checkout-pagamentos-total')!; @@ -95,6 +89,9 @@ async function loadPagamentos() { `; + const user = getUser(); + const canReverse = user?.role === 'ADMIN' || user?.role === 'CAIXA'; + lista.innerHTML = `
@@ -106,11 +103,18 @@ async function loadPagamentos() { + ${canReverse ? '' : ''} - ${pagamentos.map((p: any) => ` - + ${pagamentos.map((p: any) => { + const estornado = p.estornado; + const rowStyle = estornado ? 'opacity:0.5;text-decoration:line-through;' : ''; + const valorStyle = estornado + ? 'text-align:right;font-weight:700;color:var(--accent-primary);' + : 'text-align:right;font-weight:700;color:var(--success);'; + return ` + - + - - `).join('')} + ${canReverse ? `` : ''} + `; + }).join('')}
Forma de Pagamento Valor ObsAção
${new Date(p.criadoEm).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit' })} @@ -120,10 +124,14 @@ async function loadPagamentos() { ${p.comanda.nomeCliente ?? '—'} ${FORMAS_PAGAMENTO_LABEL[p.formaPagamento] ?? p.formaPagamento}${formatBRL(p.valor)}${formatBRL(p.valor)} ${p.observacao ?? '—'}
${estornado + ? 'ESTORNADO' + : `` + }
@@ -136,6 +144,21 @@ async function loadPagamentos() { await (window as any).navigateTo('/orders#' + comandaId); }); }); + + lista.querySelectorAll('.checkout-btn-estornar').forEach(btn => { + btn.addEventListener('click', async (e) => { + e.preventDefault(); + const pagamentoId = (btn as HTMLElement).dataset.pagamentoId!; + if (!confirm('Deseja estornar este pagamento? A comanda será reaberta.')) return; + try { + await api.patch(`/pagamentos/${pagamentoId}/estornar`, {}); + toast('Pagamento estornado com sucesso', 'success'); + await loadPagamentos(); + } catch (err: any) { + toast(err.message, 'error'); + } + }); + }); } catch (err: any) { lista.innerHTML = `

Erro: ${err.message}

`; } diff --git a/web/src/views/dashboard.ts b/web/src/views/dashboard.ts index aca24ac..7cca893 100644 --- a/web/src/views/dashboard.ts +++ b/web/src/views/dashboard.ts @@ -1,5 +1,5 @@ import { api } from '../api'; -import { renderNavbar, toast, formatBRL } from '../utils'; +import { renderNavbar, toast, formatBRL, toDisplayDate, parseDisplayDate, todayStr } from '../utils'; export async function renderDashboard(container: HTMLElement) { container.innerHTML = ` @@ -39,9 +39,11 @@ export async function renderDashboard(container: HTMLElement) {

Relatório de Vendas

- + + até - + +
@@ -59,10 +61,10 @@ export async function renderDashboard(container: HTMLElement) { `; // Set default dates - const hoje = new Date().toISOString().split('T')[0]; - const inicioMes = new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().split('T')[0]; - (document.getElementById('rel-inicio') as HTMLInputElement).value = inicioMes; - (document.getElementById('rel-fim') as HTMLInputElement).value = hoje; + const hoje = todayStr(); + const inicioMes = hoje.substring(0, 7) + '-01'; + (document.getElementById('rel-inicio') as HTMLInputElement).value = toDisplayDate(inicioMes); + (document.getElementById('rel-fim') as HTMLInputElement).value = toDisplayDate(hoje); const loadDashboard = async () => { try { @@ -80,12 +82,16 @@ export async function renderDashboard(container: HTMLElement) { }; const loadRelatorio = async () => { - const inicio = (document.getElementById('rel-inicio') as HTMLInputElement).value; - const fim = (document.getElementById('rel-fim') as HTMLInputElement).value; + const inicio = parseDisplayDate((document.getElementById('rel-inicio') as HTMLInputElement).value); + const horaInicio = (document.getElementById('rel-hora-inicio') as HTMLInputElement).value || '15:00'; + const fim = parseDisplayDate((document.getElementById('rel-fim') as HTMLInputElement).value); + const horaFim = (document.getElementById('rel-hora-fim') as HTMLInputElement).value || '04:00'; try { const params = new URLSearchParams(); if (inicio) params.set('inicio', inicio); if (fim) params.set('fim', fim); + params.set('horaInicio', horaInicio); + params.set('horaFim', horaFim); const data = await api.get(`/relatorios/vendas?${params.toString()}`); // Resumo diff --git a/web/src/views/menu.ts b/web/src/views/menu.ts index 8d61ffd..b396543 100644 --- a/web/src/views/menu.ts +++ b/web/src/views/menu.ts @@ -43,11 +43,11 @@ export async function renderMenu(container: HTMLElement) {
- +
- +
diff --git a/web/src/views/orders.ts b/web/src/views/orders.ts index f61a1b3..6be4e08 100644 --- a/web/src/views/orders.ts +++ b/web/src/views/orders.ts @@ -1,5 +1,5 @@ import { api } from '../api'; -import { renderNavbar, toast, formatBRL, getUser, statusBadge } from '../utils'; +import { renderNavbar, toast, formatBRL, getUser, statusBadge, parseDisplayDate } from '../utils'; let localComandas: any[] = []; @@ -22,11 +22,11 @@ export async function renderOrders(container: HTMLElement) {