Files
chatc2/src/public/csat.html
T
AyronSantos 12d419de7c feat: comprehensive design system overhaul
Applied impeccable design critique, audit, harden, layout, distill, and polish across all pages.

DESIGN SYSTEM:
- Created PRODUCT.md (5 design principles, WCAG 2.1 AA target)
- Created DESIGN.md (34 color tokens, 6 radius scale, full component spec)
- Created .impeccable/design.json (sidecar with tonal ramps, shadows, motion)

ACCESSIBILITY:
- 33 aria-labels across 7 pages (was ~13)
- 63 <label> elements (was ~30)
- 6 <main> landmarks + role=navigation on all sidebars
- Esc to close modals on 4 pages
- Keyboard shortcuts (Ctrl+J/K, Ctrl+F) on chat

THEMING:
- 216 hardcoded colors replaced with var() references
- 449 !important removed from dark-mode.css
- Dark mode script unified (was duplicated 9x inline)
- All modals converted to .modal-overlay + .modal design system classes

CHAT:
- Error states for messages (.msg.erro, .msg.enviando)
- Connection status indicator
- Confirmation modal before finalizar
- Input bar reorganized (3 visible actions, + menu)
- Right panel accordions
- Send button disabled when empty
- Touch targets 44px on mobile

CLIENT DETAIL:
- Tabs/badges/sub-tabs use CSS classes instead of inline styles
- 'Iniciar Conversa' button now primary action
- alert() replaced with inline feedback
- Modal converted to design system classes

CLIENT LIST:
- Modal moved inside <body> (was HTML-invalid)
- alert() replaced with inline feedback
- Modal uses .show pattern

SETTINGS:
- 4 modals converted to design system classes
- 14 alert() calls replaced with inline feedback
- fecharModal() uses classList instead of style.display

ROUTES:
- border-radius normalized, hardcoded colors fixed

Score progression:
  Chat:        23 → 32/40
  Client List: 32 → 35/40
  Client Detail: 26 → 30/40
  Settings:     29 → 35/40
  Routes:       37 → 38/40
  Audit (project): 14/20 → 17/20
2026-06-23 16:58:12 +00:00

212 lines
6.1 KiB
HTML

<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Avalie seu Atendimento</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: #fff;
border-radius: 20px;
padding: 40px;
max-width: 480px;
width: 100%;
text-align: center;
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
}
.logo { font-size: 48px; margin-bottom: 16px; }
h1 { font-size: 24px; color: var(--text-primary); margin-bottom: 8px; }
.subtitle { font-size: 14px; color: var(--text-muted); margin-bottom: 32px; }
.stars {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 32px;
direction: rtl;
}
.stars input { display: none; }
.stars label {
font-size: 48px;
cursor: pointer;
color: #d1d5db;
transition: color .2s, transform .15s;
}
.stars label:hover,
.stars label:hover ~ label,
.stars input:checked ~ label {
color: #f59e0b;
transform: scale(1.1);
}
.stars input:checked + label {
color: #f59e0b;
}
textarea {
width: 100%;
padding: 14px 16px;
border: 2px solid var(--border);
border-radius: 12px;
font-size: 14px;
font-family: inherit;
resize: vertical;
min-height: 80px;
outline: none;
transition: border-color .2s;
margin-bottom: 20px;
}
textarea:focus { border-color: var(--primary); }
.btn {
width: 100%;
padding: 14px;
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
color: #fff;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: opacity .2s;
}
.btn:hover { opacity: .9; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.rating-text {
font-size: 14px;
color: var(--text-muted);
margin-bottom: 24px;
min-height: 20px;
}
.success { display: none; }
.success .icon { font-size: 64px; margin-bottom: 16px; }
.success h2 { color: var(--success); margin-bottom: 8px; }
.success p { color: var(--text-muted); }
.erro {
color: var(--danger);
font-size: 14px;
margin-top: 12px;
display: none;
}
</style>
</head>
<body>
<div class="container" id="app">
<div class="logo">💬</div>
<h1>Avalie seu Atendimento</h1>
<p class="subtitle">Sua opinião é muito importante para melhorarmos nosso serviço</p>
<div class="rating-text" id="ratingText">Toque nas estrelas para avaliar</div>
<div class="stars" id="starContainer">
<input type="radio" name="star" id="star5" value="5">
<label for="star5" title="Excelente"></label>
<input type="radio" name="star" id="star4" value="4">
<label for="star4" title="Bom"></label>
<input type="radio" name="star" id="star3" value="3">
<label for="star3" title="Regular"></label>
<input type="radio" name="star" id="star2" value="2">
<label for="star2" title="Ruim"></label>
<input type="radio" name="star" id="star1" value="1">
<label for="star1" title="Péssimo"></label>
</div>
<textarea id="comentario" placeholder="Deixe seu comentário (opcional)..."></textarea>
<button class="btn" id="btnEnviar" onclick="enviar()">Enviar Avaliação</button>
<div class="erro" id="erro"></div>
<div class="success" id="success">
<div class="icon"></div>
<h2>Agradecemos sua avaliação!</h2>
<p>Seu feedback nos ajuda a melhorar cada vez mais.</p>
</div>
</div>
<script>
var alias, conversaId, empresaId;
function getParams() {
var params = new URLSearchParams(window.location.search);
alias = params.get('alias');
conversaId = params.get('conversa');
empresaId = params.get('empresa');
if (!alias || !conversaId || !empresaId) {
document.getElementById('erro').textContent = 'Link inválido. Entre em contato conosco.';
document.getElementById('erro').style.display = 'block';
document.querySelector('.stars').style.display = 'none';
document.querySelector('textarea').style.display = 'none';
document.getElementById('btnEnviar').style.display = 'none';
}
}
var nota = 0;
document.querySelectorAll('.stars input').forEach(function(input) {
input.addEventListener('change', function() {
nota = parseInt(this.value);
var textos = ['', 'Péssimo', 'Ruim', 'Regular', 'Bom', 'Excelente!'];
document.getElementById('ratingText').textContent = textos[nota] || '';
});
});
async function enviar() {
if (nota === 0) {
document.getElementById('erro').textContent = 'Selecione uma avaliação de 1 a 5 estrelas.';
document.getElementById('erro').style.display = 'block';
return;
}
var btn = document.getElementById('btnEnviar');
btn.disabled = true;
btn.textContent = 'Enviando...';
document.getElementById('erro').style.display = 'none';
try {
var resp = await fetch('/api/' + alias + '/csat/avaliar', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
conversaId: parseInt(conversaId),
empresaId: parseInt(empresaId),
nota: nota,
comentario: document.getElementById('comentario').value.trim()
})
});
var data = await resp.json();
if (data.success) {
document.getElementById('app').querySelector('.stars').style.display = 'none';
document.querySelector('textarea').style.display = 'none';
document.getElementById('btnEnviar').style.display = 'none';
document.getElementById('ratingText').style.display = 'none';
document.getElementById('success').style.display = 'block';
} else {
throw new Error(data.error || 'Erro ao enviar');
}
} catch(e) {
document.getElementById('erro').textContent = 'Erro ao enviar: ' + e.message;
document.getElementById('erro').style.display = 'block';
btn.disabled = false;
btn.textContent = 'Enviar Avaliação';
}
}
getParams();
</script>
<!-- impeccable-live-start -->
<script src="http://localhost:8400/live.js"></script>
<!-- impeccable-live-end -->
</body>
</html>