Vendaweb-portal/lib/utils.ts

435 lines
12 KiB
TypeScript
Raw Permalink Normal View History

2026-01-08 12:09:16 +00:00
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/**
* Validação de CPF
* @param cpf - CPF a ser validado (com ou sem formatação)
* @returns true se válido, false caso contrário
*/
export function validateCPF(cpf: string): boolean {
if (!cpf) return false;
// Remove caracteres não numéricos
const cleanCPF = cpf.replace(/[^\d]/g, "");
// Verifica se tem 11 dígitos
if (cleanCPF.length !== 11) return false;
// Verifica se todos os dígitos são iguais
if (/^(\d)\1{10}$/.test(cleanCPF)) return false;
// Validação dos dígitos verificadores
let sum = 0;
let remainder;
for (let i = 1; i <= 9; i++) {
sum += parseInt(cleanCPF.substring(i - 1, i)) * (11 - i);
}
remainder = (sum * 10) % 11;
if (remainder === 10 || remainder === 11) remainder = 0;
if (remainder !== parseInt(cleanCPF.substring(9, 10))) return false;
sum = 0;
for (let i = 1; i <= 10; i++) {
sum += parseInt(cleanCPF.substring(i - 1, i)) * (12 - i);
}
remainder = (sum * 10) % 11;
if (remainder === 10 || remainder === 11) remainder = 0;
if (remainder !== parseInt(cleanCPF.substring(10, 11))) return false;
return true;
}
/**
* Validação de CNPJ
* @param cnpj - CNPJ a ser validado (com ou sem formatação)
* @returns true se válido, false caso contrário
*/
export function validateCNPJ(cnpj: string): boolean {
if (!cnpj) return false;
// Remove caracteres não numéricos
const cleanCNPJ = cnpj.replace(/[^\d]/g, "");
// Verifica se tem 14 dígitos
if (cleanCNPJ.length !== 14) return false;
// Verifica se todos os dígitos são iguais
if (/^(\d)\1{13}$/.test(cleanCNPJ)) return false;
// Validação dos dígitos verificadores
let length = cleanCNPJ.length - 2;
let numbers = cleanCNPJ.substring(0, length);
const digits = cleanCNPJ.substring(length);
let sum = 0;
let pos = length - 7;
for (let i = length; i >= 1; i--) {
sum += parseInt(numbers.charAt(length - i)) * pos--;
if (pos < 2) pos = 9;
}
let result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
if (result !== parseInt(digits.charAt(0))) return false;
length = length + 1;
numbers = cleanCNPJ.substring(0, length);
sum = 0;
pos = length - 7;
for (let i = length; i >= 1; i--) {
sum += parseInt(numbers.charAt(length - i)) * pos--;
if (pos < 2) pos = 9;
}
result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
if (result !== parseInt(digits.charAt(1))) return false;
return true;
}
/**
* Validação de CPF ou CNPJ
* @param document - CPF ou CNPJ a ser validado
* @returns true se válido, false caso contrário
*/
export function validateCPForCNPJ(document: string): boolean {
if (!document) return false;
const cleanDoc = document.replace(/[^\d]/g, "");
if (cleanDoc.length === 11) {
return validateCPF(document);
} else if (cleanDoc.length === 14) {
return validateCNPJ(document);
}
return false;
}
/**
* Validação de CEP
* @param cep - CEP a ser validado (com ou sem formatação)
* @returns true se válido, false caso contrário
*/
export function validateCEP(cep: string): boolean {
if (!cep) return false;
const cleanCEP = cep.replace(/[^\d]/g, "");
return cleanCEP.length === 8;
}
/**
* Validação de telefone/celular
* @param phone - Telefone a ser validado
* @returns true se válido, false caso contrário
*/
export function validatePhone(phone: string): boolean {
if (!phone) return false;
const cleanPhone = phone.replace(/[^\d]/g, "");
// Aceita telefone com 10 ou 11 dígitos (com ou sem DDD)
return cleanPhone.length >= 10 && cleanPhone.length <= 11;
}
/**
* Validação de campo obrigatório
* @param value - Valor a ser validado
* @returns true se preenchido, false caso contrário
*/
export function validateRequired(value: any): boolean {
if (value === null || value === undefined) return false;
if (typeof value === "string") {
return value.trim().length > 0;
}
return true;
}
/**
* Validação de email
* @param email - Email a ser validado
* @returns true se válido, false caso contrário
*/
export function validateEmail(email: string): boolean {
if (!email) return false;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Validação de número mínimo de caracteres
* @param value - Valor a ser validado
* @param minLength - Tamanho mínimo
* @returns true se válido, false caso contrário
*/
export function validateMinLength(value: string, minLength: number): boolean {
if (!value) return false;
return value.trim().length >= minLength;
}
/**
* Validação de número máximo de caracteres
* @param value - Valor a ser validado
* @param maxLength - Tamanho máximo
* @returns true se válido, false caso contrário
*/
export function validateMaxLength(value: string, maxLength: number): boolean {
if (!value) return true; // Campo vazio é válido (usar validateRequired separadamente)
return value.length <= maxLength;
}
/**
* Validação de data mínima (data não pode ser anterior à data mínima)
* @param date - Data a ser validada
* @param minDate - Data mínima permitida
* @returns true se válido, false caso contrário
*/
export function validateMinDate(
date: Date | string | null,
minDate: Date | string
): boolean {
if (!date) return false;
const dateObj = typeof date === "string" ? new Date(date) : date;
const minDateObj = typeof minDate === "string" ? new Date(minDate) : minDate;
return dateObj >= minDateObj;
}
/**
* Validação de valor numérico mínimo
* @param value - Valor a ser validado
* @param min - Valor mínimo
* @returns true se válido, false caso contrário
*/
export function validateMinValue(value: number | string, min: number): boolean {
const numValue = typeof value === "string" ? parseFloat(value) : value;
if (isNaN(numValue)) return false;
return numValue >= min;
}
/**
* Validação de valor numérico máximo
* @param value - Valor a ser validado
* @param max - Valor máximo
* @returns true se válido, false caso contrário
*/
export function validateMaxValue(value: number | string, max: number): boolean {
const numValue = typeof value === "string" ? parseFloat(value) : value;
if (isNaN(numValue)) return false;
return numValue <= max;
}
/**
* Validação de formulário de cliente
* @param formData - Dados do formulário
* @returns objeto com isValid e errors
*/
export function validateCustomerForm(formData: {
name?: string;
document?: string;
cellPhone?: string;
cep?: string;
address?: string;
number?: string;
city?: string;
state?: string;
complement?: string;
}): { isValid: boolean; errors: Record<string, string> } {
const errors: Record<string, string> = {};
if (!validateRequired(formData.name)) {
errors.name = "Nome do cliente é obrigatório";
}
if (!validateRequired(formData.document)) {
errors.document = "CPF/CNPJ é obrigatório";
} else if (!validateCPForCNPJ(formData.document || "")) {
errors.document = "CPF/CNPJ inválido";
}
if (!validateRequired(formData.cellPhone)) {
errors.cellPhone = "Contato é obrigatório";
} else if (!validatePhone(formData.cellPhone || "")) {
errors.cellPhone = "Telefone inválido";
}
if (!validateRequired(formData.cep)) {
errors.cep = "CEP é obrigatório";
} else if (!validateCEP(formData.cep || "")) {
errors.cep = "CEP inválido";
}
if (!validateRequired(formData.address)) {
errors.address = "Endereço é obrigatório";
}
if (!validateRequired(formData.number)) {
errors.number = "Número é obrigatório";
}
if (!validateRequired(formData.city)) {
errors.city = "Cidade é obrigatória";
}
if (!validateRequired(formData.state)) {
errors.state = "Estado é obrigatório";
}
return {
isValid: Object.keys(errors).length === 0,
errors,
};
}
/**
* Validação de formulário de endereço de entrega
* @param formData - Dados do formulário
* @returns objeto com isValid e errors
*/
export function validateAddressForm(formData: {
zipCode?: string;
address?: string;
number?: string;
city?: string;
state?: string;
complement?: string;
referencePoint?: string;
note?: string;
}): { isValid: boolean; errors: Record<string, string> } {
const errors: Record<string, string> = {};
if (!validateRequired(formData.zipCode)) {
errors.zipCode = "CEP é obrigatório";
} else if (!validateCEP(formData.zipCode || "")) {
errors.zipCode = "CEP inválido";
}
if (!validateRequired(formData.address)) {
errors.address = "Endereço é obrigatório";
}
if (!validateRequired(formData.number)) {
errors.number = "Número é obrigatório";
}
if (!validateRequired(formData.city)) {
errors.city = "Cidade é obrigatória";
}
if (!validateRequired(formData.state)) {
errors.state = "Estado é obrigatório";
}
return {
isValid: Object.keys(errors).length === 0,
errors,
};
}
/**
* Validação de formulário de pagamento
* @param formData - Dados do formulário
* @returns objeto com isValid e errors
*/
export function validatePaymentForm(formData: {
invoiceStore?: any;
billing?: any;
paymentPlan?: any;
partner?: any;
}): { isValid: boolean; errors: Record<string, string> } {
const errors: Record<string, string> = {};
if (!formData.billing) {
errors.billing = "Cobrança é obrigatória";
}
if (!formData.paymentPlan) {
errors.paymentPlan = "Plano de pagamento é obrigatório";
}
return {
isValid: Object.keys(errors).length === 0,
errors,
};
}
/**
* Validação completa do pedido antes de criar
* Verifica se cliente, endereço, plano de pagamento e cobrança estão preenchidos
* @returns objeto com isValid, message e description
*/
export function validateOrder(): {
isValid: boolean;
message: string;
description: string;
} {
const customer = localStorage.getItem("customer");
const address = localStorage.getItem("address");
const paymentPlan = localStorage.getItem("paymentPlan");
const billing = localStorage.getItem("billing");
if (!customer) {
return {
isValid: false,
message: "Atenção! Não foi informado um cliente para a venda.",
description:
"Para gerar um pedido de venda é necessário selecionar um cliente.",
};
}
if (!paymentPlan) {
return {
isValid: false,
message: "Atenção! Não foi informado um plano de pagamento para a venda.",
description:
"Para gerar um pedido de venda é necessário selecionar um plano de pagamento para o pedido.",
};
}
if (!billing) {
return {
isValid: false,
message: "Atenção! Não foi informado uma cobrança para a venda.",
description:
"Para gerar um pedido de venda é necessário selecionar uma cobrança.",
};
}
return {
isValid: true,
message: "",
description: "",
};
}
/**
* Mascara CPF/CNPJ mostrando apenas os 4 primeiros dígitos
* Exemplo: 033.379.292-00 -> 033.3xx.xxx-xx
* Exemplo: 12.345.678/0001-90 -> 12.34x.xxx/xxxx-xx
* @param document - CPF ou CNPJ a ser mascarado
* @returns Documento mascarado
*/
export function maskDocument(document: string): string {
if (!document) return "";
// Remove formatação
const cleanDoc = document.replace(/[^\d]/g, "");
if (cleanDoc.length === 11) {
// CPF: 000.000.000-00
// Mostra os 4 primeiros dígitos: 000.0xx.xxx-xx
const first4 = cleanDoc.substring(0, 4);
return `${first4.substring(0, 3)}.${first4.substring(3, 4)}xx.xxx-xx`;
} else if (cleanDoc.length === 14) {
// CNPJ: 00.000.000/0000-00
// Mostra os 4 primeiros dígitos: 00.00x.xxx/xxxx-xx
const first4 = cleanDoc.substring(0, 4);
return `${first4.substring(0, 2)}.${first4.substring(2, 4)}x.xxx/xxxx-xx`;
}
// Se não for CPF nem CNPJ, retorna o documento original
return document;
}