From 616fe42e10479972f474beb33e37fb36e4c757ce Mon Sep 17 00:00:00 2001 From: Esdras Renan Date: Mon, 10 Nov 2025 11:05:53 -0300 Subject: [PATCH] feat: modernize report scheduling UI and date inputs --- .../migration.sql | 50 +++++++ scripts/login-sim.ts | 25 ++++ .../companies/admin-companies-manager.tsx | 57 +++++++- .../admin/devices/admin-devices-overview.tsx | 41 +++--- .../devices/device-tickets-history.client.tsx | 11 +- .../admin/users/admin-users-workspace.tsx | 38 +++++- src/components/dashboard/dashboard-hero.tsx | 18 ++- .../reports/report-filter-toolbar.tsx | 31 +++-- .../reports/report-schedule-drawer.tsx | 45 ++++-- src/components/ui/date-picker.tsx | 128 ++++++++++++++++++ 10 files changed, 384 insertions(+), 60 deletions(-) create mode 100644 prisma/migrations/20251110133949_add_report_export_tables/migration.sql create mode 100644 scripts/login-sim.ts create mode 100644 src/components/ui/date-picker.tsx diff --git a/prisma/migrations/20251110133949_add_report_export_tables/migration.sql b/prisma/migrations/20251110133949_add_report_export_tables/migration.sql new file mode 100644 index 0000000..abf615b --- /dev/null +++ b/prisma/migrations/20251110133949_add_report_export_tables/migration.sql @@ -0,0 +1,50 @@ +-- CreateTable +CREATE TABLE "ReportExportSchedule" ( + "id" TEXT NOT NULL PRIMARY KEY, + "tenantId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "reportKeys" JSONB NOT NULL, + "range" TEXT NOT NULL DEFAULT '30d', + "companyId" TEXT, + "companyName" TEXT, + "format" TEXT NOT NULL DEFAULT 'xlsx', + "frequency" TEXT NOT NULL, + "dayOfWeek" INTEGER, + "dayOfMonth" INTEGER, + "hour" INTEGER NOT NULL DEFAULT 8, + "minute" INTEGER NOT NULL DEFAULT 0, + "timezone" TEXT NOT NULL DEFAULT 'America/Sao_Paulo', + "recipients" JSONB NOT NULL, + "status" TEXT NOT NULL DEFAULT 'ACTIVE', + "lastRunAt" DATETIME, + "nextRunAt" DATETIME, + "createdBy" TEXT NOT NULL, + "updatedBy" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "ReportExportRun" ( + "id" TEXT NOT NULL PRIMARY KEY, + "tenantId" TEXT NOT NULL, + "scheduleId" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'PENDING', + "startedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "completedAt" DATETIME, + "error" TEXT, + "artifacts" JSONB, + CONSTRAINT "ReportExportRun_scheduleId_fkey" FOREIGN KEY ("scheduleId") REFERENCES "ReportExportSchedule" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateIndex +CREATE INDEX "ReportExportSchedule_tenantId_status_idx" ON "ReportExportSchedule"("tenantId", "status"); + +-- CreateIndex +CREATE INDEX "ReportExportSchedule_tenantId_nextRunAt_idx" ON "ReportExportSchedule"("tenantId", "nextRunAt"); + +-- CreateIndex +CREATE INDEX "ReportExportRun_tenantId_status_idx" ON "ReportExportRun"("tenantId", "status"); + +-- CreateIndex +CREATE INDEX "ReportExportRun_tenantId_scheduleId_idx" ON "ReportExportRun"("tenantId", "scheduleId"); diff --git a/scripts/login-sim.ts b/scripts/login-sim.ts new file mode 100644 index 0000000..91af211 --- /dev/null +++ b/scripts/login-sim.ts @@ -0,0 +1,25 @@ +import { auth } from "../src/lib/auth" + +async function simulateLogin() { + const email = process.env.TEST_LOGIN_EMAIL ?? "admin@sistema.dev" + const password = process.env.TEST_LOGIN_PASSWORD ?? "admin123" + + const result = await auth.api.signInEmail({ + body: { + email, + password, + rememberMe: true, + }, + returnHeaders: true, + }) + + console.log("HTTP", result.response ? 200 : "unknown") + console.log("Session token", result.response?.token) + console.log("User", result.response?.user) + console.log("Cookies", Array.from(result.headers.entries())) +} + +simulateLogin().catch((error) => { + console.error("Failed to simulate login:", error) + process.exit(1) +}) diff --git a/src/components/admin/companies/admin-companies-manager.tsx b/src/components/admin/companies/admin-companies-manager.tsx index 8135ddd..9a8fff0 100644 --- a/src/components/admin/companies/admin-companies-manager.tsx +++ b/src/components/admin/companies/admin-companies-manager.tsx @@ -1,6 +1,7 @@ "use client" import { useCallback, useEffect, useMemo, useRef, useState, useTransition } from "react" +import dynamic from "next/dynamic" import { Controller, FormProvider, useFieldArray, useForm, type UseFormReturn } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { @@ -86,7 +87,8 @@ import { useAuth } from "@/lib/auth-client" import type { Id } from "@/convex/_generated/dataModel" import { api } from "@/convex/_generated/api" import { MultiValueInput } from "@/components/ui/multi-value-input" -import { AdminDevicesOverview } from "@/components/admin/devices/admin-devices-overview" +import { Spinner } from "@/components/ui/spinner" +import { DatePicker } from "@/components/ui/date-picker" type LastAlertInfo = { createdAt: number; usagePct: number; threshold: number } | null @@ -101,6 +103,22 @@ type EditorState = | { mode: "create" } | { mode: "edit"; company: NormalizedCompany } +const AdminDevicesOverview = dynamic( + () => + import("@/components/admin/devices/admin-devices-overview").then( + (mod) => mod.AdminDevicesOverview + ), + { + ssr: false, + loading: () => ( +
+ + Carregando dispositivos... +
+ ), + } +) + const BOARD_COLUMNS = [ { id: "monthly", title: "Mensalistas", description: "Contratos recorrentes ou planos mensais." }, { id: "time_bank", title: "Banco de horas", description: "Clientes com consumo controlado por horas." }, @@ -1790,17 +1808,48 @@ function CompanySheet({ tenantId, editor, onClose, onCreated, onUpdated }: Compa
- + ( + field.onChange(next ?? null)} + placeholder="Selecionar data" + /> + )} + />
- + ( + field.onChange(next ?? null)} + placeholder="Selecionar data" + /> + )} + />
- + ( + field.onChange(next ?? null)} + placeholder="Selecionar data" + allowClear + /> + )} + />
diff --git a/src/components/admin/devices/admin-devices-overview.tsx b/src/components/admin/devices/admin-devices-overview.tsx index 45d872b..fc42ea7 100644 --- a/src/components/admin/devices/admin-devices-overview.tsx +++ b/src/components/admin/devices/admin-devices-overview.tsx @@ -72,6 +72,8 @@ import type { Id } from "@/convex/_generated/dataModel" import { TicketStatusBadge } from "@/components/tickets/status-badge" import type { TicketPriority, TicketStatus } from "@/lib/schemas/ticket" import { DeviceCustomFieldManager } from "@/components/admin/devices/device-custom-field-manager" +import { DatePicker } from "@/components/ui/date-picker" +import { SearchableCombobox, type SearchableComboboxOption } from "@/components/ui/searchable-combobox" type DeviceMetrics = Record | null @@ -1342,6 +1344,13 @@ export function AdminDevicesOverview({ tenantId, initialCompanyFilterSlug = "all .sort((a, b) => a.name.localeCompare(b.name, "pt-BR")) }, [companies, devices]) + const companyComboboxOptions = useMemo(() => { + return companyOptions.map((company) => ({ + value: company.slug, + label: company.name, + })) + }, [companyOptions]) + const deviceFields = useQuery( api.deviceFields.listForTenant, convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip" @@ -2250,23 +2259,17 @@ export function AdminDevicesOverview({ tenantId, initialCompanyFilterSlug = "all
- + />