feat: enhance chat components with improved accessibility features, including ARIA attributes for search and user status, and implement message length validation and file type checks in message input handling
This commit is contained in:
142
apps/web/src/lib/utils/avatarCache.ts
Normal file
142
apps/web/src/lib/utils/avatarCache.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Cache de avatares para reduzir requisições repetidas
|
||||
* Usa Cache API do navegador e cache em memória
|
||||
*/
|
||||
|
||||
interface AvatarCacheEntry {
|
||||
url: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
// Cache em memória (útil durante a sessão)
|
||||
const memoryCache = new Map<string, AvatarCacheEntry>();
|
||||
|
||||
// Nome do cache no Cache API
|
||||
const CACHE_NAME = 'sgse-avatars-v1';
|
||||
const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 dias
|
||||
|
||||
/**
|
||||
* Obtém avatar do cache ou faz requisição
|
||||
*/
|
||||
export async function getCachedAvatar(
|
||||
avatarUrl: string | null | undefined,
|
||||
userId?: string
|
||||
): Promise<string | null> {
|
||||
if (!avatarUrl) return null;
|
||||
|
||||
// Usar userId como chave se disponível, senão usar a URL
|
||||
const cacheKey = userId || avatarUrl;
|
||||
|
||||
// Verificar cache em memória primeiro
|
||||
const memoryEntry = memoryCache.get(cacheKey);
|
||||
if (memoryEntry && Date.now() - memoryEntry.timestamp < CACHE_DURATION) {
|
||||
return memoryEntry.url;
|
||||
}
|
||||
|
||||
// Verificar Cache API do navegador
|
||||
if (typeof window !== 'undefined' && 'caches' in window) {
|
||||
try {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
const cachedResponse = await cache.match(avatarUrl);
|
||||
|
||||
if (cachedResponse) {
|
||||
const blob = await cachedResponse.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Atualizar cache em memória
|
||||
memoryCache.set(cacheKey, {
|
||||
url,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
return url;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Erro ao acessar cache de avatares:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Se não está no cache, fazer requisição e armazenar
|
||||
try {
|
||||
const response = await fetch(avatarUrl);
|
||||
if (!response.ok) return null;
|
||||
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Armazenar no cache em memória
|
||||
memoryCache.set(cacheKey, {
|
||||
url,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Armazenar no Cache API
|
||||
if (typeof window !== 'undefined' && 'caches' in window) {
|
||||
try {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
await cache.put(avatarUrl, new Response(blob));
|
||||
} catch (error) {
|
||||
console.warn('Erro ao armazenar avatar no cache:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
} catch (error) {
|
||||
console.warn('Erro ao carregar avatar:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpa o cache de avatares
|
||||
*/
|
||||
export async function clearAvatarCache(): Promise<void> {
|
||||
memoryCache.clear();
|
||||
|
||||
if (typeof window !== 'undefined' && 'caches' in window) {
|
||||
try {
|
||||
await caches.delete(CACHE_NAME);
|
||||
} catch (error) {
|
||||
console.warn('Erro ao limpar cache de avatares:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpa avatares antigos do cache (mais de 7 dias)
|
||||
*/
|
||||
export async function cleanOldAvatars(): Promise<void> {
|
||||
const now = Date.now();
|
||||
|
||||
// Limpar cache em memória
|
||||
for (const [key, entry] of memoryCache.entries()) {
|
||||
if (now - entry.timestamp > CACHE_DURATION) {
|
||||
URL.revokeObjectURL(entry.url);
|
||||
memoryCache.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Limpar Cache API (manter apenas os últimos 100)
|
||||
if (typeof window !== 'undefined' && 'caches' in window) {
|
||||
try {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
const keys = await cache.keys();
|
||||
|
||||
// Se há mais de 100 avatares, remover os mais antigos
|
||||
if (keys.length > 100) {
|
||||
const toDelete = keys.slice(0, keys.length - 100);
|
||||
await Promise.all(toDelete.map((key) => cache.delete(key)));
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Erro ao limpar cache antigo:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Limpar cache antigo periodicamente (a cada hora)
|
||||
if (typeof window !== 'undefined') {
|
||||
setInterval(() => {
|
||||
cleanOldAvatars();
|
||||
}, 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
@@ -434,3 +434,5 @@ export function adicionarRodape(doc: jsPDF): void {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ async function measureNetworkLatency(): Promise<number> {
|
||||
const start = performance.now();
|
||||
|
||||
// Fazer uma requisição pequena para medir latência
|
||||
await fetch(window.location.origin + '/favicon.ico', {
|
||||
await fetch(window.location.origin + '/favicon.png', {
|
||||
method: 'HEAD',
|
||||
cache: 'no-cache'
|
||||
});
|
||||
|
||||
@@ -97,6 +97,11 @@ export async function gerarPDFComSelecao(
|
||||
yPosition = adicionarDadosFuncionario(doc, yPosition, funcionario, dataInicio, dataFim);
|
||||
}
|
||||
|
||||
// SEÇÃO: TABELA PRINCIPAL DE REGISTROS (PRIMEIRO)
|
||||
if (sections.registrosPonto) {
|
||||
yPosition = gerarTabelaRegistrosPDF(doc, yPosition, dias, configPonto, sections);
|
||||
}
|
||||
|
||||
// Resumo do Período
|
||||
yPosition = adicionarResumoPeriodo(doc, yPosition, resumo, formatarHoras, formatarMinutos);
|
||||
|
||||
@@ -106,11 +111,6 @@ export async function gerarPDFComSelecao(
|
||||
// Legenda
|
||||
yPosition = adicionarLegenda(doc, yPosition);
|
||||
|
||||
// SEÇÃO: TABELA PRINCIPAL DE REGISTROS
|
||||
if (sections.registrosPonto) {
|
||||
yPosition = gerarTabelaRegistrosPDF(doc, yPosition, dias, configPonto, sections);
|
||||
}
|
||||
|
||||
// SEÇÃO: BANCO DE HORAS
|
||||
if (sections.bancoHoras) {
|
||||
yPosition = await gerarSecaoBancoHorasPDF(
|
||||
|
||||
Reference in New Issue
Block a user