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
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:
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user