feat: complete UI/UX refinement, email flow updates, and deep black theme
All checks were successful
Build and Deploy / build-and-push (push) Successful in 2m18s

- Updated all email templates to a clean light theme and changed button text to 'Finalizar Cadastro'.

- Enforced a strict 15-minute expiration on all auth/reset tokens.

- Created SetupAccount flow distinct from ResetPassword to capture user name during admin init.

- Refined dark mode to a premium True Black (Onyx) palette using Zinc.

- Fixed Dashboard KPI visibility and true period-over-period trend logic.

- Enhanced TeamManagement with global tenant filtering for Super Admins.

- Implemented secure User URL routing via slugs instead of raw UUIDs.

- Enforced strict Agent-level RBAC for viewing attendances.
This commit is contained in:
Cauê Faleiros
2026-03-05 15:33:03 -03:00
parent d5b57835a7
commit c4bd4d58a1
14 changed files with 369 additions and 70 deletions

View File

@@ -1,9 +1,9 @@
import React, { useState, useMemo } from 'react';
import {
Building2, Users, MessageSquare, Plus, Search,
Edit, Trash2, ChevronDown, ChevronUp, ChevronsUpDown, X
Edit, Trash2, ChevronDown, ChevronUp, ChevronsUpDown, X, CheckCircle2
} from 'lucide-react';
import { getTenants, createTenant, deleteTenant } from '../services/dataService';
import { getTenants, createTenant, deleteTenant, updateTenant } from '../services/dataService';
import { Tenant } from '../types';
import { DateRangePicker } from '../components/DateRangePicker';
import { KPICard } from '../components/KPICard';
@@ -91,21 +91,44 @@ export const SuperAdmin: React.FC = () => {
}
};
const [successMessage, setSuccessMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const handleSaveTenant = async (e: React.FormEvent) => {
e.preventDefault();
setErrorMessage('');
setSuccessMessage('');
const form = e.target as HTMLFormElement;
const name = (form.elements.namedItem('name') as HTMLInputElement).value;
const slug = (form.elements.namedItem('slug') as HTMLInputElement).value;
const admin_email = (form.elements.namedItem('admin_email') as HTMLInputElement).value;
const status = (form.elements.namedItem('status') as HTMLSelectElement).value;
const success = await createTenant({ name, slug, admin_email, status });
if (success) {
setIsModalOpen(false);
setEditingTenant(null);
loadTenants();
alert('Organização salva com sucesso!');
if (editingTenant) {
const success = await updateTenant(editingTenant.id, { name, slug, admin_email, status });
if (success) {
setSuccessMessage('Organização atualizada com sucesso!');
loadTenants();
setTimeout(() => {
setIsModalOpen(false);
setSuccessMessage('');
setEditingTenant(null);
}, 2000);
} else {
setErrorMessage('Erro ao atualizar organização.');
}
} else {
alert('Erro ao salvar organização.');
const result = await createTenant({ name, slug, admin_email, status });
if (result.success) {
setSuccessMessage(result.message || 'Organização criada com sucesso!');
loadTenants();
setTimeout(() => {
setIsModalOpen(false);
setSuccessMessage('');
}, 3000);
} else {
setErrorMessage(result.message || 'Erro ao salvar organização.');
}
}
};
@@ -132,7 +155,7 @@ export const SuperAdmin: React.FC = () => {
<h1 className="text-2xl font-bold text-zinc-900 dark:text-zinc-50 tracking-tight">Painel Super Admin</h1>
<p className="text-zinc-500 dark:text-dark-muted text-sm">Gerencie organizações e visualize estatísticas globais.</p>
</div>
<button onClick={() => { setEditingTenant(null); setIsModalOpen(true); }} className="bg-zinc-900 dark:bg-brand-yellow text-white dark:text-zinc-950 px-4 py-2 rounded-lg text-sm font-bold hover:opacity-90 transition-all shadow-sm">
<button onClick={() => { setEditingTenant(null); setIsModalOpen(true); }} className="bg-zinc-900 dark:bg-brand-yellow text-white dark:text-zinc-950 px-4 py-2 rounded-lg flex items-center gap-2 whitespace-nowrap text-sm font-bold hover:opacity-90 transition-all shadow-sm">
<Plus size={16} /> Adicionar Organização
</button>
</div>
@@ -225,6 +248,16 @@ export const SuperAdmin: React.FC = () => {
<button onClick={() => setIsModalOpen(false)} className="text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"><X size={20} /></button>
</div>
<form onSubmit={handleSaveTenant} className="p-6 space-y-4">
{successMessage && (
<div className="bg-green-50 dark:bg-green-900/20 border border-green-100 dark:border-green-900/30 p-3 rounded-lg flex items-center gap-2 text-green-700 dark:text-green-400 text-sm animate-in fade-in slide-in-from-top-1">
<CheckCircle2 size={16} /> {successMessage}
</div>
)}
{errorMessage && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-100 dark:border-red-900/30 p-3 rounded-lg flex items-center gap-2 text-red-700 dark:text-red-400 text-sm animate-in fade-in slide-in-from-top-1">
<X size={16} /> {errorMessage}
</div>
)}
<div className="space-y-2">
<label className="text-sm font-medium text-zinc-700 dark:text-zinc-300">Nome da Organização</label>
<input type="text" name="name" defaultValue={editingTenant?.name} className="w-full px-3 py-2 bg-zinc-50 dark:bg-dark-bg border border-zinc-200 dark:border-dark-border rounded-lg text-sm text-zinc-900 dark:text-zinc-100 focus:ring-2 focus:ring-brand-yellow/20 outline-none transition-all" required />