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 } { const errors: Record = {}; 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 } { const errors: Record = {}; 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 } { const errors: Record = {}; 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; }