435 lines
12 KiB
TypeScript
435 lines
12 KiB
TypeScript
|
|
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;
|
||
|
|
}
|