forked from git.gladyson/openmonetis
refactor: migrate from ESLint to Biome and extract SQL queries to data.ts
- Replace ESLint with Biome for linting and formatting - Configure Biome with tabs, double quotes, and organized imports - Move all SQL/Drizzle queries from page.tsx files to data.ts files - Create new data.ts files for: ajustes, dashboard, relatorios/categorias - Update existing data.ts files: extrato, fatura (add lancamentos queries) - Remove all drizzle-orm imports from page.tsx files - Update README.md with new tooling info Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,127 +8,129 @@ import { z } from "zod";
|
||||
* UUID schema with custom error message
|
||||
*/
|
||||
export const uuidSchema = (entityName: string = "ID") =>
|
||||
z.string({ message: `${entityName} inválido.` }).uuid(`${entityName} inválido.`);
|
||||
z
|
||||
.string({ message: `${entityName} inválido.` })
|
||||
.uuid(`${entityName} inválido.`);
|
||||
|
||||
/**
|
||||
* Decimal string schema - parses string with comma/period to number
|
||||
*/
|
||||
export const decimalSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.transform((value) => value.replace(/\s/g, "").replace(",", "."))
|
||||
.refine(
|
||||
(value) => !Number.isNaN(Number.parseFloat(value)),
|
||||
"Informe um valor numérico válido."
|
||||
)
|
||||
.transform((value) => Number.parseFloat(value));
|
||||
.string()
|
||||
.trim()
|
||||
.transform((value) => value.replace(/\s/g, "").replace(",", "."))
|
||||
.refine(
|
||||
(value) => !Number.isNaN(Number.parseFloat(value)),
|
||||
"Informe um valor numérico válido.",
|
||||
)
|
||||
.transform((value) => Number.parseFloat(value));
|
||||
|
||||
/**
|
||||
* Optional/nullable decimal string schema
|
||||
*/
|
||||
export const optionalDecimalSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.transform((value) =>
|
||||
value && value.length > 0 ? value.replace(",", ".") : null
|
||||
)
|
||||
.refine(
|
||||
(value) => value === null || !Number.isNaN(Number.parseFloat(value)),
|
||||
"Informe um valor numérico válido."
|
||||
)
|
||||
.transform((value) => (value === null ? null : Number.parseFloat(value)));
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.transform((value) =>
|
||||
value && value.length > 0 ? value.replace(",", ".") : null,
|
||||
)
|
||||
.refine(
|
||||
(value) => value === null || !Number.isNaN(Number.parseFloat(value)),
|
||||
"Informe um valor numérico válido.",
|
||||
)
|
||||
.transform((value) => (value === null ? null : Number.parseFloat(value)));
|
||||
|
||||
/**
|
||||
* Day of month schema (1-31)
|
||||
*/
|
||||
export const dayOfMonthSchema = z
|
||||
.string({ message: "Informe o dia." })
|
||||
.trim()
|
||||
.min(1, "Informe o dia.")
|
||||
.refine((value) => {
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return !Number.isNaN(parsed) && parsed >= 1 && parsed <= 31;
|
||||
}, "Informe um dia entre 1 e 31.");
|
||||
.string({ message: "Informe o dia." })
|
||||
.trim()
|
||||
.min(1, "Informe o dia.")
|
||||
.refine((value) => {
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return !Number.isNaN(parsed) && parsed >= 1 && parsed <= 31;
|
||||
}, "Informe um dia entre 1 e 31.");
|
||||
|
||||
/**
|
||||
* Period schema (YYYY-MM format)
|
||||
*/
|
||||
export const periodSchema = z
|
||||
.string({ message: "Informe o período." })
|
||||
.trim()
|
||||
.regex(/^\d{4}-(0[1-9]|1[0-2])$/, "Período inválido.");
|
||||
.string({ message: "Informe o período." })
|
||||
.trim()
|
||||
.regex(/^\d{4}-(0[1-9]|1[0-2])$/, "Período inválido.");
|
||||
|
||||
/**
|
||||
* Optional period schema
|
||||
*/
|
||||
export const optionalPeriodSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^(\d{4})-(\d{2})$/, {
|
||||
message: "Selecione um período válido.",
|
||||
})
|
||||
.optional();
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^(\d{4})-(\d{2})$/, {
|
||||
message: "Selecione um período válido.",
|
||||
})
|
||||
.optional();
|
||||
|
||||
/**
|
||||
* Date string schema
|
||||
*/
|
||||
export const dateStringSchema = z
|
||||
.string({ message: "Informe a data." })
|
||||
.trim()
|
||||
.refine((value) => !Number.isNaN(new Date(value).getTime()), {
|
||||
message: "Data inválida.",
|
||||
});
|
||||
.string({ message: "Informe a data." })
|
||||
.trim()
|
||||
.refine((value) => !Number.isNaN(new Date(value).getTime()), {
|
||||
message: "Data inválida.",
|
||||
});
|
||||
|
||||
/**
|
||||
* Optional date string schema
|
||||
*/
|
||||
export const optionalDateStringSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.refine((value) => !value || !Number.isNaN(new Date(value).getTime()), {
|
||||
message: "Informe uma data válida.",
|
||||
})
|
||||
.optional();
|
||||
.string()
|
||||
.trim()
|
||||
.refine((value) => !value || !Number.isNaN(new Date(value).getTime()), {
|
||||
message: "Informe uma data válida.",
|
||||
})
|
||||
.optional();
|
||||
|
||||
/**
|
||||
* Note/observation schema (max 500 chars, trimmed, nullable)
|
||||
*/
|
||||
export const noteSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.max(500, "A anotação deve ter no máximo 500 caracteres.")
|
||||
.optional()
|
||||
.transform((value) => (value && value.length > 0 ? value : null));
|
||||
.string()
|
||||
.trim()
|
||||
.max(500, "A anotação deve ter no máximo 500 caracteres.")
|
||||
.optional()
|
||||
.transform((value) => (value && value.length > 0 ? value : null));
|
||||
|
||||
/**
|
||||
* Optional string that becomes null if empty
|
||||
*/
|
||||
export const optionalStringToNull = z
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.transform((value) => (value && value.length > 0 ? value : null));
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.transform((value) => (value && value.length > 0 ? value : null));
|
||||
|
||||
/**
|
||||
* Required non-empty string schema
|
||||
*/
|
||||
export const requiredStringSchema = (fieldName: string) =>
|
||||
z
|
||||
.string({ message: `Informe ${fieldName}.` })
|
||||
.trim()
|
||||
.min(1, `Informe ${fieldName}.`);
|
||||
z
|
||||
.string({ message: `Informe ${fieldName}.` })
|
||||
.trim()
|
||||
.min(1, `Informe ${fieldName}.`);
|
||||
|
||||
/**
|
||||
* Amount schema with minimum value validation
|
||||
*/
|
||||
export const amountSchema = z.coerce
|
||||
.number({ message: "Informe o valor." })
|
||||
.min(0, "Informe um valor maior ou igual a zero.");
|
||||
.number({ message: "Informe o valor." })
|
||||
.min(0, "Informe um valor maior ou igual a zero.");
|
||||
|
||||
/**
|
||||
* Positive amount schema
|
||||
*/
|
||||
export const positiveAmountSchema = z.coerce
|
||||
.number({ message: "Informe o valor." })
|
||||
.positive("Informe um valor maior que zero.");
|
||||
.number({ message: "Informe o valor." })
|
||||
.positive("Informe um valor maior que zero.");
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const inboxItemSchema = z.object({
|
||||
sourceApp: z.string().min(1, "sourceApp é obrigatório"),
|
||||
sourceAppName: z.string().optional(),
|
||||
originalTitle: z.string().optional(),
|
||||
originalText: z.string().min(1, "originalText é obrigatório"),
|
||||
notificationTimestamp: z.string().transform((val) => new Date(val)),
|
||||
parsedName: z.string().optional(),
|
||||
parsedAmount: z.coerce.number().optional(),
|
||||
parsedTransactionType: z.enum(["Despesa", "Receita"]).optional(),
|
||||
clientId: z.string().optional(), // ID local do app para rastreamento
|
||||
sourceApp: z.string().min(1, "sourceApp é obrigatório"),
|
||||
sourceAppName: z.string().optional(),
|
||||
originalTitle: z.string().optional(),
|
||||
originalText: z.string().min(1, "originalText é obrigatório"),
|
||||
notificationTimestamp: z.string().transform((val) => new Date(val)),
|
||||
parsedName: z.string().optional(),
|
||||
parsedAmount: z.coerce.number().optional(),
|
||||
parsedTransactionType: z.enum(["Despesa", "Receita"]).optional(),
|
||||
clientId: z.string().optional(), // ID local do app para rastreamento
|
||||
});
|
||||
|
||||
export const inboxBatchSchema = z.object({
|
||||
items: z.array(inboxItemSchema).min(1).max(50),
|
||||
items: z.array(inboxItemSchema).min(1).max(50),
|
||||
});
|
||||
|
||||
export type InboxItemInput = z.infer<typeof inboxItemSchema>;
|
||||
|
||||
@@ -4,30 +4,30 @@ import { z } from "zod";
|
||||
* Categorias de insights
|
||||
*/
|
||||
export const INSIGHT_CATEGORIES = {
|
||||
behaviors: {
|
||||
id: "behaviors",
|
||||
title: "Comportamentos Observados",
|
||||
icon: "RiEyeLine",
|
||||
color: "blue",
|
||||
},
|
||||
triggers: {
|
||||
id: "triggers",
|
||||
title: "Gatilhos de Consumo",
|
||||
icon: "RiFlashlightLine",
|
||||
color: "amber",
|
||||
},
|
||||
recommendations: {
|
||||
id: "recommendations",
|
||||
title: "Recomendações Práticas",
|
||||
icon: "RiLightbulbLine",
|
||||
color: "green",
|
||||
},
|
||||
improvements: {
|
||||
id: "improvements",
|
||||
title: "Melhorias Sugeridas",
|
||||
icon: "RiRocketLine",
|
||||
color: "purple",
|
||||
},
|
||||
behaviors: {
|
||||
id: "behaviors",
|
||||
title: "Comportamentos Observados",
|
||||
icon: "RiEyeLine",
|
||||
color: "blue",
|
||||
},
|
||||
triggers: {
|
||||
id: "triggers",
|
||||
title: "Gatilhos de Consumo",
|
||||
icon: "RiFlashlightLine",
|
||||
color: "amber",
|
||||
},
|
||||
recommendations: {
|
||||
id: "recommendations",
|
||||
title: "Recomendações Práticas",
|
||||
icon: "RiLightbulbLine",
|
||||
color: "green",
|
||||
},
|
||||
improvements: {
|
||||
id: "improvements",
|
||||
title: "Melhorias Sugeridas",
|
||||
icon: "RiRocketLine",
|
||||
color: "purple",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type InsightCategoryId = keyof typeof INSIGHT_CATEGORIES;
|
||||
@@ -36,29 +36,29 @@ export type InsightCategoryId = keyof typeof INSIGHT_CATEGORIES;
|
||||
* Schema para item individual de insight
|
||||
*/
|
||||
export const InsightItemSchema = z.object({
|
||||
text: z.string().min(1),
|
||||
text: z.string().min(1),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema para categoria de insights
|
||||
*/
|
||||
export const InsightCategorySchema = z.object({
|
||||
category: z.enum([
|
||||
"behaviors",
|
||||
"triggers",
|
||||
"recommendations",
|
||||
"improvements",
|
||||
]),
|
||||
items: z.array(InsightItemSchema).min(1).max(6),
|
||||
category: z.enum([
|
||||
"behaviors",
|
||||
"triggers",
|
||||
"recommendations",
|
||||
"improvements",
|
||||
]),
|
||||
items: z.array(InsightItemSchema).min(1).max(6),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for complete insights response from AI
|
||||
*/
|
||||
export const InsightsResponseSchema = z.object({
|
||||
month: z.string().regex(/^\d{4}-\d{2}$/), // YYYY-MM
|
||||
generatedAt: z.string(), // ISO datetime
|
||||
categories: z.array(InsightCategorySchema).length(4),
|
||||
month: z.string().regex(/^\d{4}-\d{2}$/), // YYYY-MM
|
||||
generatedAt: z.string(), // ISO datetime
|
||||
categories: z.array(InsightCategorySchema).length(4),
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user