Update dependencies and enhance Dashboard and Landing pages with new features and UI improvements
This commit is contained in:
@@ -34,7 +34,12 @@ import {
|
||||
ChevronDown,
|
||||
UserPlus,
|
||||
Globe,
|
||||
TrendingUp,
|
||||
HelpCircle,
|
||||
MessageCircle,
|
||||
Settings,
|
||||
Store,
|
||||
Clock,
|
||||
ArrowRight,
|
||||
} from 'lucide-react';
|
||||
|
||||
const periods: Record<string, (date: Date) => boolean> = {
|
||||
@@ -107,34 +112,39 @@ export default function Dashboard() {
|
||||
// Agendamentos concluídos (histórico)
|
||||
const completedAppointments = allShopAppointments.filter((a) => a.status === 'concluido');
|
||||
|
||||
// Estatísticas para lista de marcações
|
||||
const todayAppointments = appointments.filter((a) => {
|
||||
// Estatísticas para lista de marcações (do dia selecionado)
|
||||
const selectedDateAppointments = appointments.filter((a) => {
|
||||
if (a.shopId !== shop.id) return false;
|
||||
const aptDate = new Date(a.date.replace(' ', 'T'));
|
||||
const today = new Date();
|
||||
return (
|
||||
aptDate.getDate() === today.getDate() &&
|
||||
aptDate.getMonth() === today.getMonth() &&
|
||||
aptDate.getFullYear() === today.getFullYear()
|
||||
aptDate.getDate() === selectedDate.getDate() &&
|
||||
aptDate.getMonth() === selectedDate.getMonth() &&
|
||||
aptDate.getFullYear() === selectedDate.getFullYear()
|
||||
);
|
||||
});
|
||||
|
||||
const totalBookingsToday = todayAppointments.filter((a) => includeCancelled || a.status !== 'cancelado').length;
|
||||
const totalBookingsToday = selectedDateAppointments.filter((a) => includeCancelled || a.status !== 'cancelado').length;
|
||||
|
||||
const newClientsToday = useMemo(() => {
|
||||
const clientIds = new Set(todayAppointments.map((a) => a.customerId));
|
||||
const clientIds = new Set(selectedDateAppointments.map((a) => a.customerId));
|
||||
return clientIds.size;
|
||||
}, [todayAppointments]);
|
||||
const onlineBookingsToday = todayAppointments.filter((a) => a.status !== 'cancelado').length;
|
||||
}, [selectedDateAppointments]);
|
||||
|
||||
const onlineBookingsToday = selectedDateAppointments.filter((a) => a.status !== 'cancelado').length;
|
||||
|
||||
const occupancyRate = useMemo(() => {
|
||||
// Calcular ocupação baseada em slots disponíveis (8h-18h = 20 slots de 30min)
|
||||
const totalSlots = 20;
|
||||
const bookedSlots = todayAppointments.filter((a) => a.status !== 'cancelado').length;
|
||||
const bookedSlots = selectedDateAppointments.filter((a) => a.status !== 'cancelado').length;
|
||||
return Math.round((bookedSlots / totalSlots) * 100);
|
||||
}, [todayAppointments]);
|
||||
}, [selectedDateAppointments]);
|
||||
|
||||
// Comparação com semana passada (simplificado - sempre 0% por enquanto)
|
||||
const comparisonPercent = 0;
|
||||
|
||||
// Filtrar agendamentos para lista
|
||||
const filteredAppointments = useMemo(() => {
|
||||
let filtered = shopAppointments;
|
||||
let filtered = selectedDateAppointments;
|
||||
|
||||
if (!includeCancelled) {
|
||||
filtered = filtered.filter((a) => a.status !== 'cancelado');
|
||||
@@ -157,7 +167,8 @@ export default function Dashboard() {
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}, [shopAppointments, includeCancelled, searchQuery, shop.services, shop.barbers, users]);
|
||||
}, [selectedDateAppointments, includeCancelled, searchQuery, shop.services, shop.barbers, users]);
|
||||
|
||||
// Pedidos apenas com produtos (não serviços)
|
||||
const shopOrders = orders.filter(
|
||||
(o) => o.shopId === shop.id && periodMatch(new Date(o.createdAt)) && o.items.some((item) => item.type === 'product')
|
||||
@@ -295,109 +306,238 @@ export default function Dashboard() {
|
||||
{/* Tab Content */}
|
||||
{activeTab === 'overview' && (
|
||||
<div className="space-y-6">
|
||||
{/* Stats Cards */}
|
||||
<div className="grid md:grid-cols-4 gap-4">
|
||||
<Card className="p-5 bg-gradient-to-br from-amber-50 to-white">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="p-2 bg-amber-500 rounded-lg text-white">
|
||||
<TrendingUp size={20} />
|
||||
{/* Saudação */}
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-2xl font-bold text-slate-900">
|
||||
{new Date().toLocaleDateString('pt-PT', { weekday: 'long', day: 'numeric', month: 'long' })}
|
||||
</h1>
|
||||
<p className="text-slate-600">
|
||||
{(() => {
|
||||
const hour = new Date().getHours();
|
||||
if (hour < 12) return 'Bom dia';
|
||||
if (hour < 18) return 'Boa tarde';
|
||||
return 'Boa noite';
|
||||
})()}, {user?.name}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards Principais */}
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="p-3 bg-indigo-500 rounded-lg text-white">
|
||||
<Calendar size={24} />
|
||||
</div>
|
||||
<Badge color="amber" variant="soft">Período</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 mb-1">Faturamento</p>
|
||||
<p className="text-2xl font-bold text-amber-700">{currency(totalRevenue)}</p>
|
||||
<p className="text-sm text-slate-600 mb-1">Total de reservas</p>
|
||||
<p className="text-3xl font-bold text-slate-900">{allShopAppointments.length}</p>
|
||||
<p className="text-xs text-slate-500 mt-2">Reservas da plataforma: {allShopAppointments.length}</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-5">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="p-2 bg-blue-500 rounded-lg text-white">
|
||||
<Calendar size={20} />
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="p-3 bg-blue-500 rounded-lg text-white">
|
||||
<Globe size={24} />
|
||||
</div>
|
||||
<Badge color="amber" variant="soft">{pendingAppts}</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 mb-1">Pendentes</p>
|
||||
<p className="text-2xl font-bold text-slate-900">{pendingAppts}</p>
|
||||
<p className="text-sm text-slate-600 mb-1">Reservas online</p>
|
||||
<p className="text-3xl font-bold text-slate-900">{allShopAppointments.length}</p>
|
||||
<p className="text-xs text-slate-500 mt-2">Marcações feitas pela plataforma</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-5">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="p-2 bg-emerald-500 rounded-lg text-white">
|
||||
<Calendar size={20} />
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="p-3 bg-green-500 rounded-lg text-white">
|
||||
<UserPlus size={24} />
|
||||
</div>
|
||||
<Badge color="green" variant="soft">{confirmedAppts}</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 mb-1">Confirmados</p>
|
||||
<p className="text-2xl font-bold text-slate-900">{confirmedAppts}</p>
|
||||
</Card>
|
||||
|
||||
<Card className={`p-5 ${lowStock.length > 0 ? 'bg-amber-50 border-amber-200' : ''}`}>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className={`p-2 rounded-lg text-white ${lowStock.length > 0 ? 'bg-amber-500' : 'bg-slate-500'}`}>
|
||||
<AlertTriangle size={20} />
|
||||
</div>
|
||||
{lowStock.length > 0 && <Badge color="amber" variant="solid">{lowStock.length}</Badge>}
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 mb-1">Stock baixo</p>
|
||||
<p className={`text-2xl font-bold ${lowStock.length > 0 ? 'text-amber-700' : 'text-slate-900'}`}>{lowStock.length}</p>
|
||||
<p className="text-sm text-slate-600 mb-1">Novos clientes</p>
|
||||
<p className="text-3xl font-bold text-slate-900">
|
||||
{new Set(allShopAppointments.map(a => a.customerId)).size}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 mt-2">Clientes únicos</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Charts */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-4">Serviços vs Produtos</h3>
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={comparisonData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<Tooltip />
|
||||
<Bar dataKey="value" fill="#f59e0b" radius={[6, 6, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Card className="p-5">
|
||||
<h3 className="text-base font-bold text-slate-900 mb-3">Top 5 Serviços</h3>
|
||||
<div className="space-y-2">
|
||||
{topServices.length > 0 ? (
|
||||
topServices.map((s, idx) => (
|
||||
<div key={s.name} className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-slate-400 font-bold">#{idx + 1}</span>
|
||||
<span className="text-slate-700">{s.name}</span>
|
||||
</div>
|
||||
<Badge color="amber">{s.qty} vendas</Badge>
|
||||
{/* Layout em duas colunas */}
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{/* Coluna Principal - Esquerda */}
|
||||
<div className="md:col-span-2 space-y-6">
|
||||
{/* Reservas de Hoje */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 bg-indigo-100 rounded-lg">
|
||||
<Search size={20} className="text-indigo-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-slate-900">Reservas de hoje</h3>
|
||||
<p className="text-sm text-slate-600">Verá aqui as reservas de hoje assim que chegarem</p>
|
||||
</div>
|
||||
</div>
|
||||
{(() => {
|
||||
const today = new Date();
|
||||
const todayAppts = allShopAppointments.filter(a => {
|
||||
const aptDate = new Date(a.date.replace(' ', 'T'));
|
||||
return aptDate.toDateString() === today.toDateString();
|
||||
});
|
||||
|
||||
if (todayAppts.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-8">
|
||||
<Calendar size={48} className="mx-auto text-slate-300 mb-3" />
|
||||
<p className="text-slate-600 font-medium mb-2">Sem reservas hoje</p>
|
||||
<Button variant="outline" size="sm" onClick={() => setActiveTab('appointments')}>
|
||||
Ir para o calendário
|
||||
<ArrowRight size={16} className="ml-2" />
|
||||
</Button>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-slate-500">Sem vendas no período</p>
|
||||
)}
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{todayAppts.slice(0, 3).map(a => {
|
||||
const svc = shop.services.find(s => s.id === a.serviceId);
|
||||
const barber = shop.barbers.find(b => b.id === a.barberId);
|
||||
const customer = users.find(u => u.id === a.customerId);
|
||||
const aptDate = new Date(a.date.replace(' ', 'T'));
|
||||
const timeStr = aptDate.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
return (
|
||||
<div key={a.id} className="flex items-center justify-between p-3 border border-slate-200 rounded-lg hover:bg-slate-50">
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-slate-900">{customer?.name || 'Cliente'}</p>
|
||||
<p className="text-sm text-slate-600">{timeStr} · {svc?.name || 'Serviço'}</p>
|
||||
</div>
|
||||
<Badge color={a.status === 'pendente' ? 'amber' : a.status === 'confirmado' ? 'green' : 'red'}>
|
||||
{a.status}
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{todayAppts.length > 3 && (
|
||||
<Button variant="outline" size="sm" className="w-full" onClick={() => setActiveTab('appointments')}>
|
||||
Ver todas ({todayAppts.length})
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Coluna Lateral - Direita */}
|
||||
<div className="space-y-6">
|
||||
{/* Ajuda */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-4">Tem alguma pergunta?</h3>
|
||||
<div className="space-y-3">
|
||||
<button className="w-full flex items-center gap-3 p-3 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors text-left">
|
||||
<div className="p-2 bg-indigo-100 rounded-lg">
|
||||
<HelpCircle size={18} className="text-indigo-600" />
|
||||
</div>
|
||||
<span className="font-medium text-slate-900">Centro de ajuda</span>
|
||||
</button>
|
||||
<button className="w-full flex items-center gap-3 p-3 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors text-left">
|
||||
<div className="p-2 bg-blue-100 rounded-lg">
|
||||
<MessageCircle size={18} className="text-blue-600" />
|
||||
</div>
|
||||
<span className="font-medium text-slate-900">Contacte-nos</span>
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-5">
|
||||
<h3 className="text-base font-bold text-slate-900 mb-3">Top 5 Produtos</h3>
|
||||
<div className="space-y-2">
|
||||
{topProducts.length > 0 ? (
|
||||
topProducts.map((p, idx) => (
|
||||
<div key={p.name} className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-slate-400 font-bold">#{idx + 1}</span>
|
||||
<span className="text-slate-700">{p.name}</span>
|
||||
</div>
|
||||
<Badge color="amber">{p.qty} vendas</Badge>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-slate-500">Sem vendas no período</p>
|
||||
)}
|
||||
{/* Atalhos */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-4">Atalhos</h3>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
onClick={() => setActiveTab('services')}
|
||||
className="flex flex-col items-center gap-2 p-4 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors"
|
||||
>
|
||||
<div className="p-2 bg-indigo-100 rounded-lg">
|
||||
<Scissors size={20} className="text-indigo-600" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-900">Serviços</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('appointments')}
|
||||
className="flex flex-col items-center gap-2 p-4 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors"
|
||||
>
|
||||
<div className="p-2 bg-blue-100 rounded-lg">
|
||||
<Users size={20} className="text-blue-600" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-900">Clientes</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('appointments')}
|
||||
className="flex flex-col items-center gap-2 p-4 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors"
|
||||
>
|
||||
<div className="p-2 bg-green-100 rounded-lg">
|
||||
<Calendar size={20} className="text-green-600" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-900">Calendário</span>
|
||||
</button>
|
||||
<button className="flex flex-col items-center gap-2 p-4 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors">
|
||||
<div className="p-2 bg-purple-100 rounded-lg">
|
||||
<Settings size={20} className="text-purple-600" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-900">Definições</span>
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Próximos Agendamentos */}
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-4">Seguinte</h3>
|
||||
{(() => {
|
||||
const upcoming = allShopAppointments
|
||||
.filter(a => {
|
||||
const aptDate = new Date(a.date.replace(' ', 'T'));
|
||||
return aptDate > new Date() && a.status !== 'cancelado';
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const dateA = new Date(a.date.replace(' ', 'T'));
|
||||
const dateB = new Date(b.date.replace(' ', 'T'));
|
||||
return dateA.getTime() - dateB.getTime();
|
||||
})
|
||||
.slice(0, 3);
|
||||
|
||||
if (upcoming.length === 0) {
|
||||
return (
|
||||
<p className="text-sm text-slate-500 text-center py-4">Sem agendamentos futuros</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{upcoming.map(a => {
|
||||
const svc = shop.services.find(s => s.id === a.serviceId);
|
||||
const barber = shop.barbers.find(b => b.id === a.barberId);
|
||||
const customer = users.find(u => u.id === a.customerId);
|
||||
const aptDate = new Date(a.date.replace(' ', 'T'));
|
||||
const timeStr = aptDate.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
return (
|
||||
<div key={a.id} className="flex items-start gap-3 p-3 border border-slate-200 rounded-lg">
|
||||
<div className="p-1.5 bg-indigo-100 rounded">
|
||||
<Clock size={14} className="text-indigo-600" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-semibold text-slate-900 truncate">{customer?.name || 'Cliente'}</p>
|
||||
<p className="text-sm text-slate-600">{timeStr}</p>
|
||||
<p className="text-xs text-slate-500 truncate">{svc?.name || 'Serviço'}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -442,10 +582,15 @@ export default function Dashboard() {
|
||||
<div className="p-2 bg-indigo-500 rounded-lg text-white">
|
||||
<Calendar size={20} />
|
||||
</div>
|
||||
<span className="text-xs text-slate-500">0%</span>
|
||||
<span className={`text-xs ${comparisonPercent >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{comparisonPercent >= 0 ? '+' : ''}{comparisonPercent}%
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 mb-1">Total de marcações</p>
|
||||
<p className="text-2xl font-bold text-slate-900">{totalBookingsToday}</p>
|
||||
<p className="text-xs text-slate-500 mt-1">
|
||||
Comparado com {totalBookingsToday} no mesmo dia da semana passada
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-5">
|
||||
@@ -453,10 +598,15 @@ export default function Dashboard() {
|
||||
<div className="p-2 bg-green-500 rounded-lg text-white">
|
||||
<UserPlus size={20} />
|
||||
</div>
|
||||
<span className="text-xs text-slate-500">0%</span>
|
||||
<span className={`text-xs ${comparisonPercent >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{comparisonPercent >= 0 ? '+' : ''}{comparisonPercent}%
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 mb-1">Novos clientes</p>
|
||||
<p className="text-2xl font-bold text-slate-900">{newClientsToday}</p>
|
||||
<p className="text-xs text-slate-500 mt-1">
|
||||
Comparado com {newClientsToday} no mesmo dia da semana passada
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-5">
|
||||
@@ -464,10 +614,15 @@ export default function Dashboard() {
|
||||
<div className="p-2 bg-blue-500 rounded-lg text-white">
|
||||
<Globe size={20} />
|
||||
</div>
|
||||
<span className="text-xs text-slate-500">0%</span>
|
||||
<span className={`text-xs ${comparisonPercent >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{comparisonPercent >= 0 ? '+' : ''}{comparisonPercent}%
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 mb-1">Marcações online</p>
|
||||
<p className="text-2xl font-bold text-slate-900">{onlineBookingsToday}</p>
|
||||
<p className="text-xs text-slate-500 mt-1">
|
||||
Comparado com {onlineBookingsToday} no mesmo dia da semana passada
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-5">
|
||||
@@ -475,10 +630,15 @@ export default function Dashboard() {
|
||||
<div className="p-2 bg-purple-500 rounded-lg text-white">
|
||||
<TrendingUp size={20} />
|
||||
</div>
|
||||
<span className="text-xs text-slate-500">0%</span>
|
||||
<span className={`text-xs ${comparisonPercent >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{comparisonPercent >= 0 ? '+' : ''}{comparisonPercent}%
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-slate-600 mb-1">Ocupação</p>
|
||||
<p className="text-2xl font-bold text-slate-900">{occupancyRate}%</p>
|
||||
<p className="text-xs text-slate-500 mt-1">
|
||||
Comparado com {occupancyRate}% no mesmo dia da semana passada
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -489,7 +649,7 @@ export default function Dashboard() {
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" size={18} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Pesquisar por cliente, serviço ou barbeiro..."
|
||||
placeholder="Pesquisar por cliente"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full rounded-lg border border-slate-300 bg-white px-10 py-2.5 text-sm text-slate-900 placeholder:text-slate-400 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/30 transition-all"
|
||||
@@ -528,7 +688,7 @@ export default function Dashboard() {
|
||||
}}>
|
||||
<ChevronLeft size={18} />
|
||||
</Button>
|
||||
<div className="text-sm font-medium text-slate-700">
|
||||
<div className="text-sm font-medium text-slate-700 px-3">
|
||||
{selectedDate.toLocaleDateString('pt-PT', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
@@ -630,7 +790,7 @@ export default function Dashboard() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-16">
|
||||
<Calendar size={64} className="mx-auto text-slate-300 mb-4" />
|
||||
<Calendar size={64} className="mx-auto text-indigo-300 mb-4" />
|
||||
<p className="text-lg font-semibold text-slate-900 mb-2">Sem reservas</p>
|
||||
<p className="text-sm text-slate-600 max-w-md mx-auto">
|
||||
Ambas as suas reservas online e manuais aparecerão aqui
|
||||
|
||||
@@ -90,25 +90,25 @@ export default function Landing() {
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{[
|
||||
{
|
||||
icon: Calendar,
|
||||
{[
|
||||
{
|
||||
icon: Calendar,
|
||||
title: 'Agendamentos Inteligentes',
|
||||
desc: 'Escolha serviço, barbeiro, data e horário com validação de slots em tempo real. Notificações automáticas.',
|
||||
color: 'from-blue-500 to-blue-600'
|
||||
},
|
||||
{
|
||||
icon: ShoppingBag,
|
||||
title: 'Carrinho Inteligente',
|
||||
color: 'from-blue-500 to-blue-600'
|
||||
},
|
||||
{
|
||||
icon: ShoppingBag,
|
||||
title: 'Carrinho Inteligente',
|
||||
desc: 'Produtos e serviços agrupados por barbearia, checkout rápido e seguro. Histórico completo de compras.',
|
||||
color: 'from-emerald-500 to-emerald-600'
|
||||
},
|
||||
{
|
||||
icon: BarChart3,
|
||||
title: 'Painel Completo',
|
||||
color: 'from-emerald-500 to-emerald-600'
|
||||
},
|
||||
{
|
||||
icon: BarChart3,
|
||||
title: 'Painel Completo',
|
||||
desc: 'Faturamento, agendamentos, pedidos e análises detalhadas. Tudo no controle da sua barbearia.',
|
||||
color: 'from-purple-500 to-purple-600'
|
||||
},
|
||||
color: 'from-purple-500 to-purple-600'
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: 'Gestão de Barbeiros',
|
||||
@@ -127,17 +127,17 @@ export default function Landing() {
|
||||
desc: 'Dados protegidos, pagamentos seguros e backup automático. Conformidade com LGPD.',
|
||||
color: 'from-rose-500 to-rose-600'
|
||||
},
|
||||
].map((feature) => (
|
||||
<Card key={feature.title} hover className="p-6 space-y-4 group">
|
||||
<div className={`inline-flex p-3 rounded-xl bg-gradient-to-br ${feature.color} text-white shadow-lg group-hover:scale-110 transition-transform duration-200`}>
|
||||
<feature.icon size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-slate-900 mb-2">{feature.title}</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">{feature.desc}</p>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
].map((feature) => (
|
||||
<Card key={feature.title} hover className="p-6 space-y-4 group">
|
||||
<div className={`inline-flex p-3 rounded-xl bg-gradient-to-br ${feature.color} text-white shadow-lg group-hover:scale-110 transition-transform duration-200`}>
|
||||
<feature.icon size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-slate-900 mb-2">{feature.title}</h3>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">{feature.desc}</p>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user