177 lines
4.9 KiB
TypeScript
177 lines
4.9 KiB
TypeScript
// Helper functions for input masks and validations
|
|
|
|
/** Remove all non-digit characters from string */
|
|
export const onlyDigits = (value: string): string => {
|
|
return (value || "").replace(/\D/g, "");
|
|
};
|
|
|
|
/** Format CPF: 000.000.000-00 */
|
|
export const maskCPF = (value: string): string => {
|
|
const digits = onlyDigits(value).slice(0, 11);
|
|
return digits
|
|
.replace(/(\d{3})(\d)/, "$1.$2")
|
|
.replace(/(\d{3})(\d)/, "$1.$2")
|
|
.replace(/(\d{3})(\d{1,2})$/, "$1-$2");
|
|
};
|
|
|
|
/** Validate CPF format and checksum */
|
|
export const validateCPF = (value: string): boolean => {
|
|
const digits = onlyDigits(value);
|
|
|
|
if (digits.length !== 11 || /^([0-9])\1+$/.test(digits)) {
|
|
return false;
|
|
}
|
|
|
|
const calculateDigit = (base: string, factor: number): number => {
|
|
let sum = 0;
|
|
for (let i = 0; i < base.length; i++) {
|
|
sum += parseInt(base[i]) * (factor - i);
|
|
}
|
|
const rest = (sum * 10) % 11;
|
|
return rest === 10 ? 0 : rest;
|
|
};
|
|
|
|
const digit1 = calculateDigit(digits.slice(0, 9), 10);
|
|
const digit2 = calculateDigit(digits.slice(0, 10), 11);
|
|
|
|
return digits[9] === String(digit1) && digits[10] === String(digit2);
|
|
};
|
|
|
|
/** Format CEP: 00000-000 */
|
|
export const maskCEP = (value: string): string => {
|
|
const digits = onlyDigits(value).slice(0, 8);
|
|
return digits.replace(/(\d{5})(\d{1,3})$/, "$1-$2");
|
|
};
|
|
|
|
/** Format phone: (00) 0000-0000 or (00) 00000-0000 */
|
|
export const maskPhone = (value: string): string => {
|
|
const digits = onlyDigits(value).slice(0, 11);
|
|
|
|
if (digits.length <= 10) {
|
|
return digits
|
|
.replace(/(\d{2})(\d)/, "($1) $2")
|
|
.replace(/(\d{4})(\d{1,4})$/, "$1-$2");
|
|
}
|
|
|
|
return digits
|
|
.replace(/(\d{2})(\d)/, "($1) $2")
|
|
.replace(/(\d{5})(\d{1,4})$/, "$1-$2");
|
|
};
|
|
|
|
/** Format date: dd/mm/aaaa */
|
|
export const maskDate = (value: string): string => {
|
|
const digits = onlyDigits(value).slice(0, 8);
|
|
return digits
|
|
.replace(/(\d{2})(\d)/, "$1/$2")
|
|
.replace(/(\d{2})(\d{1,4})$/, "$1/$2");
|
|
};
|
|
|
|
/** Validate date in format dd/mm/aaaa */
|
|
export const validateDate = (value: string): boolean => {
|
|
const match = value.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
|
|
if (!match) return false;
|
|
|
|
const day = Number(match[1]);
|
|
const month = Number(match[2]) - 1;
|
|
const year = Number(match[3]);
|
|
|
|
const date = new Date(year, month, day);
|
|
|
|
return (
|
|
date.getFullYear() === year &&
|
|
date.getMonth() === month &&
|
|
date.getDate() === day
|
|
);
|
|
};
|
|
|
|
/** Format UF: uppercase, max 2 chars */
|
|
export const maskUF = (value: string): string => {
|
|
return (value || "").toUpperCase().replace(/[^A-Z]/g, "").slice(0, 2);
|
|
};
|
|
|
|
/** Format RG by UF */
|
|
const rgFormatByUF: Record<string, [number, number, number, number]> = {
|
|
RJ: [2, 3, 2, 1],
|
|
SP: [2, 3, 3, 1],
|
|
MG: [2, 3, 3, 1],
|
|
ES: [2, 3, 3, 1],
|
|
PR: [2, 3, 3, 1],
|
|
SC: [2, 3, 3, 1],
|
|
RS: [2, 3, 3, 1],
|
|
BA: [2, 3, 3, 1],
|
|
PE: [2, 3, 3, 1],
|
|
CE: [2, 3, 3, 1],
|
|
PA: [2, 3, 3, 1],
|
|
AM: [2, 3, 3, 1],
|
|
AC: [2, 3, 3, 1],
|
|
AP: [2, 3, 3, 1],
|
|
AL: [2, 3, 3, 1],
|
|
RN: [2, 3, 3, 1],
|
|
PB: [2, 3, 3, 1],
|
|
MA: [2, 3, 3, 1],
|
|
PI: [2, 3, 3, 1],
|
|
DF: [2, 3, 3, 1],
|
|
GO: [2, 3, 3, 1],
|
|
MT: [2, 3, 3, 1],
|
|
MS: [2, 3, 3, 1],
|
|
RO: [2, 3, 3, 1],
|
|
RR: [2, 3, 3, 1],
|
|
TO: [2, 3, 3, 1],
|
|
};
|
|
|
|
export const maskRGByUF = (uf: string, value: string): string => {
|
|
const raw = (value || "").toUpperCase().replace(/[^0-9X]/g, "");
|
|
const [a, b, c, dv] = rgFormatByUF[uf?.toUpperCase()] ?? [2, 3, 3, 1];
|
|
const baseMax = a + b + c;
|
|
const baseDigits = raw.replace(/X/g, "").slice(0, baseMax);
|
|
const verifier = raw.slice(baseDigits.length, baseDigits.length + dv).slice(0, 1);
|
|
|
|
const g1 = baseDigits.slice(0, a);
|
|
const g2 = baseDigits.slice(a, a + b);
|
|
const g3 = baseDigits.slice(a + b, a + b + c);
|
|
|
|
let formatted = g1;
|
|
if (g2) formatted += `.${g2}`;
|
|
if (g3) formatted += `.${g3}`;
|
|
if (verifier) formatted += `-${verifier}`;
|
|
|
|
return formatted;
|
|
};
|
|
|
|
export const padRGLeftByUF = (uf: string, value: string): string => {
|
|
const raw = (value || "").toUpperCase().replace(/[^0-9X]/g, "");
|
|
const [a, b, c, dv] = rgFormatByUF[uf?.toUpperCase()] ?? [2, 3, 3, 1];
|
|
const baseMax = a + b + c;
|
|
let base = raw.replace(/X/g, "");
|
|
const verifier = raw.slice(base.length, base.length + dv).slice(0, 1);
|
|
|
|
if (base.length < baseMax) {
|
|
base = base.padStart(baseMax, "0");
|
|
}
|
|
|
|
return maskRGByUF(uf, base + (verifier || ""));
|
|
};
|
|
|
|
/** Format account number */
|
|
export const maskContaBancaria = (value: string): string => {
|
|
const digits = onlyDigits(value);
|
|
return digits;
|
|
};
|
|
|
|
/** Format zone and section for voter title */
|
|
export const maskZonaSecao = (value: string): string => {
|
|
const digits = onlyDigits(value).slice(0, 4);
|
|
return digits;
|
|
};
|
|
|
|
/** Format general numeric field */
|
|
export const maskNumeric = (value: string): string => {
|
|
return onlyDigits(value);
|
|
};
|
|
|
|
/** Remove extra spaces and trim */
|
|
export const normalizeText = (value: string): string => {
|
|
return (value || "").replace(/\s+/g, " ").trim();
|
|
};
|
|
|