feat: Add 'atas' (minutes/records) management feature, and implement various improvements across UI, backend logic, and authentication.

This commit is contained in:
2025-12-02 16:37:48 -03:00
parent 05e7f1181d
commit 4bd9e21748
265 changed files with 29156 additions and 26460 deletions

View File

@@ -7,21 +7,21 @@
* Obtém a URL base do sistema para uso em links de email
*/
function getBaseUrl(): string {
// Em produção, usar variável de ambiente
const url = process.env.FRONTEND_URL || "http://localhost:5173";
// Garantir que tenha protocolo
if (!url.match(/^https?:\/\//i)) {
return `http://${url}`;
}
return url;
// Em produção, usar variável de ambiente
const url = process.env.FRONTEND_URL || 'http://localhost:5173';
// Garantir que tenha protocolo
if (!url.match(/^https?:\/\//i)) {
return `http://${url}`;
}
return url;
}
/**
* Gera o HTML do header com logo do Governo de PE
*/
function generateHeader(): string {
const baseUrl = getBaseUrl();
return `
const baseUrl = getBaseUrl();
return `
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #1a3a52; padding: 20px 0;">
<tr>
<td align="center">
@@ -42,10 +42,10 @@ function generateHeader(): string {
* Gera o HTML do footer com assinatura SGSE
*/
function generateFooter(): string {
const baseUrl = getBaseUrl();
const currentYear = new Date().getFullYear();
return `
const baseUrl = getBaseUrl();
const currentYear = new Date().getFullYear();
return `
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #f5f5f5; border-top: 3px solid #1a3a52; margin-top: 30px;">
<tr>
<td align="center">
@@ -85,24 +85,24 @@ function generateFooter(): string {
* @returns HTML completo do email pronto para envio
*/
export function wrapEmailHTML(conteudoHTML: string, titulo?: string): string {
// Se o conteúdo já estiver dentro de um wrapper completo, retornar como está
if (conteudoHTML.includes('<!DOCTYPE html>') || conteudoHTML.includes('<html')) {
return conteudoHTML;
}
// Se o conteúdo já estiver dentro de um wrapper completo, retornar como está
if (conteudoHTML.includes('<!DOCTYPE html>') || conteudoHTML.includes('<html')) {
return conteudoHTML;
}
// Garantir que o conteúdo tenha estrutura básica
let conteudoProcessado = conteudoHTML.trim();
// Se não tiver tags HTML básicas, envolver em parágrafo
if (!conteudoProcessado.match(/^<[a-z]/i)) {
conteudoProcessado = `<p style="margin: 0 0 15px 0;">${conteudoProcessado}</p>`;
}
// Garantir que o conteúdo tenha estrutura básica
let conteudoProcessado = conteudoHTML.trim();
const header = generateHeader();
const footer = generateFooter();
const emailTitle = titulo || "Notificação do SGSE";
// Se não tiver tags HTML básicas, envolver em parágrafo
if (!conteudoProcessado.match(/^<[a-z]/i)) {
conteudoProcessado = `<p style="margin: 0 0 15px 0;">${conteudoProcessado}</p>`;
}
return `
const header = generateHeader();
const footer = generateFooter();
const emailTitle = titulo || 'Notificação do SGSE';
return `
<!DOCTYPE html>
<html lang="pt-BR">
<head>
@@ -169,17 +169,18 @@ export function wrapEmailHTML(conteudoHTML: string, titulo?: string): string {
* @returns HTML formatado
*/
export function textToHTML(texto: string): string {
return texto
.split('\n')
.map(linha => {
const linhaTrim = linha.trim();
if (!linhaTrim) return '<br />';
// Detectar links
const linkRegex = /(https?:\/\/[^\s]+)/g;
const linhaComLinks = linhaTrim.replace(linkRegex, '<a href="$1" style="color: #1a3a52; text-decoration: underline;">$1</a>');
return `<p style="margin: 0 0 15px 0;">${linhaComLinks}</p>`;
})
.join('');
return texto
.split('\n')
.map((linha) => {
const linhaTrim = linha.trim();
if (!linhaTrim) return '<br />';
// Detectar links
const linkRegex = /(https?:\/\/[^\s]+)/g;
const linhaComLinks = linhaTrim.replace(
linkRegex,
'<a href="$1" style="color: #1a3a52; text-decoration: underline;">$1</a>'
);
return `<p style="margin: 0 0 15px 0;">${linhaComLinks}</p>`;
})
.join('');
}