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
-
+ />