alterações design

This commit is contained in:
2026-03-17 10:44:37 +00:00
parent c9306f2dcb
commit bdc4b56d49
2 changed files with 207 additions and 100 deletions

View File

@@ -47,12 +47,13 @@ export default function Dashboard() {
const [prodStock, setProdStock] = useState('10');
const [barberName, setBarberName] = useState('');
const [barberSpecs, setBarberSpecs] = useState('');
const [barberSearchQuery, setBarberSearchQuery] = useState('');
// Segurança de Bloqueio - Validação estrita do role do utilziador no componente
if (!user || user.role !== 'barbearia') {
if (!user || user.role !== 'barbearia' || !shop) {
return (
<View style={styles.container}>
<Text>Área exclusiva para barbearias</Text>
<View style={[styles.container, { justifyContent: 'center', alignItems: 'center' }]}>
<Text style={{ color: '#fff', fontSize: 18 }}>A carregar dados da barbearia...</Text>
</View>
);
}
@@ -114,12 +115,11 @@ export default function Dashboard() {
if (!barberName.trim()) return;
addBarber(shop.id, {
name: barberName,
specialties: barberSpecs.split(',').map((s) => s.trim()).filter(Boolean),
specialties: [],
schedule: [],
});
setBarberName('');
setBarberSpecs('');
Alert.alert('Sucesso', 'Barbeiro adicionado');
Alert.alert('Sucesso', 'Profissional adicionado');
};
/**
@@ -141,7 +141,7 @@ export default function Dashboard() {
{ id: 'orders', label: 'Pedidos Boutique' },
{ id: 'services', label: 'Serviços' },
{ id: 'products', label: 'Produtos' },
{ id: 'barbers', label: 'Equipa' },
{ id: 'barbers', label: 'Profissionais' },
];
return (
@@ -366,38 +366,60 @@ export default function Dashboard() {
)}
{activeTab === 'barbers' && (
<View>
{shop.barbers.map((b) => (
<Card key={b.id} style={styles.itemCard}>
<View style={styles.itemHeader}>
<View>
<Text style={styles.itemName}>{b.name}</Text>
<Text style={styles.itemDesc}>
Especialidades: {b.specialties.length > 0 ? b.specialties.join(', ') : 'Nenhuma'}
</Text>
<View style={{ gap: 20 }}>
<Text style={[styles.title, { marginBottom: 0 }]}>Profissionais</Text>
{/* Barra de Pesquisa Estilo Screenshot */}
<View style={styles.searchContainer}>
<Input
placeholder="Pesquisar"
value={barberSearchQuery}
onChangeText={setBarberSearchQuery}
style={styles.searchInput}
placeholderTextColor="#64748b"
/>
</View>
{/* Lista de Profissionais */}
<View style={styles.barberList}>
{shop.barbers
.filter(b => b.name.toLowerCase().includes(barberSearchQuery.toLowerCase()))
.map((b) => (
<View key={b.id} style={styles.barberCard}>
<View style={styles.barberAvatar}>
{/* Placeholder ou imagem real se disponível futuramente */}
<Text style={{ color: '#6366f1', fontWeight: 'bold' }}>{b.name.charAt(0)}</Text>
</View>
<View style={styles.barberInfo}>
<Text style={styles.barberNameText}>{b.name}</Text>
</View>
<TouchableOpacity
onPress={() => {
Alert.alert('Confirmar', 'Deseja remover este profissional?', [
{ text: 'Cancelar', style: 'cancel' },
{ text: 'Remover', style: 'destructive', onPress: () => deleteBarber(shop.id, b.id) },
]);
}}
style={styles.deleteBarberBtn}
>
<Text style={{ color: '#ef4444', fontSize: 12 }}>Sair</Text>
</TouchableOpacity>
</View>
<Button
onPress={() => {
Alert.alert('Confirmar', 'Deseja remover este barbeiro?', [
{ text: 'Cancelar', style: 'cancel' },
{ text: 'Remover', style: 'destructive', onPress: () => deleteBarber(shop.id, b.id) },
]);
}}
variant="outline"
size="sm"
style={styles.deleteButton}
>
Remover
</Button>
</Card>
))}
))}
{shop.barbers.filter(b => b.name.toLowerCase().includes(barberSearchQuery.toLowerCase())).length === 0 && (
<View style={styles.emptyResults}>
<Text style={styles.emptyText}>Nenhum profissional encontrado.</Text>
</View>
)}
</View>
{/* Formulário de Adição */}
<Card style={styles.formCard}>
<Text style={styles.formTitle}>Adicionar barbeiro</Text>
<Text style={styles.formTitle}>Adicionar profissional</Text>
<Input label="Nome" value={barberName} onChangeText={setBarberName} placeholder="Ex: João Silva" />
<Input label="Especialidades" value={barberSpecs} onChangeText={setBarberSpecs} placeholder="Fade, Navalha, Barba" />
<Button onPress={addNewBarber} style={styles.addButton}>
Adicionar
Adicionar Coração do Negócio
</Button>
</Card>
</View>
@@ -570,6 +592,69 @@ const styles = StyleSheet.create({
stockButton: {
flex: 1,
},
searchContainer: {
marginBottom: 8,
},
searchInput: {
backgroundColor: '#0d0d0d',
borderColor: 'rgba(255,255,255,0.05)',
height: 56,
borderRadius: 16,
paddingHorizontal: 20,
color: '#fff',
fontSize: 16,
},
barberList: {
gap: 12,
},
barberCard: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#0d0d0d',
borderRadius: 24,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.05)',
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 8,
elevation: 4,
},
barberAvatar: {
width: 60,
height: 60,
borderRadius: 18,
backgroundColor: '#1e293b',
alignItems: 'center',
justifyContent: 'center',
marginRight: 16,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
},
barberInfo: {
flex: 1,
},
barberNameText: {
fontSize: 18,
fontWeight: 'bold',
color: '#fff',
letterSpacing: -0.5,
},
deleteBarberBtn: {
padding: 8,
backgroundColor: 'rgba(239, 68, 68, 0.1)',
borderRadius: 12,
},
emptyResults: {
padding: 40,
alignItems: 'center',
backgroundColor: '#0d0d0d',
borderRadius: 24,
borderStyle: 'dashed',
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
},
});

View File

@@ -141,6 +141,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
const [barberName, setBarberName] = useState('');
const [barberSpecs, setBarberSpecs] = useState('');
const [barberSearchQuery, setBarberSearchQuery] = useState('');
// Settings states
const [editShopName, setEditShopName] = useState(shop.name);
@@ -249,12 +250,11 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
if (!barberName.trim()) return;
addBarber(shop.id, {
name: barberName,
imageUrl: '', // Foto será adicionada depois
specialties: barberSpecs.split(',').map((s) => s.trim()).filter(Boolean),
imageUrl: '',
specialties: [],
schedule: [],
});
setBarberName('');
setBarberSpecs('');
};
const [uploadingBarberId, setUploadingBarberId] = useState<string | null>(null);
@@ -349,7 +349,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
{ id: 'orders', label: 'Pedidos', icon: ShoppingBag, badge: pendingOrders > 0 ? pendingOrders : undefined },
{ id: 'services', label: 'Serviços', icon: Scissors },
{ id: 'products', label: 'Produtos', icon: Package },
{ id: 'barbers', label: 'Barbeiros', icon: Users },
{ id: 'barbers', label: 'Profissionais', icon: Users },
{ id: 'settings', label: 'Definições', icon: Settings },
];
@@ -897,82 +897,104 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
{activeTab === 'barbers' && (
<div className="space-y-6">
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold text-slate-900">Barbeiros</h2>
<Badge color="slate" variant="soft">{shop.barbers.length} barbeiros</Badge>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<h2 className="text-2xl font-bold text-slate-900">Profissionais</h2>
</div>
{/* Barra de Pesquisa Estilo Screenshot */}
<div className="relative group">
<div className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-indigo-500 transition-colors">
<Search size={20} />
</div>
<div className="space-y-3 mb-6">
{shop.barbers.map((b) => (
<div key={b.id} className="p-4 border border-slate-200 rounded-lg">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-4">
<div className="relative group/avatar">
<div className="w-16 h-16 rounded-2xl overflow-hidden border-2 border-slate-100 bg-slate-50 flex items-center justify-center">
{b.imageUrl ? (
<img src={b.imageUrl} alt={b.name} className="w-full h-full object-cover" />
) : (
<Users size={24} className="text-slate-400" />
)}
</div>
<input
type="file"
id={`barber-img-${b.id}`}
className="hidden"
accept="image/*"
onChange={(e) => handleBarberImageUpload(b.id, e)}
/>
<label
htmlFor={`barber-img-${b.id}`}
className="absolute inset-0 bg-black/40 text-white flex items-center justify-center rounded-2xl opacity-0 group-hover/avatar:opacity-100 cursor-pointer transition-opacity"
>
{uploadingBarberId === b.id ? <RefreshCw size={14} className="animate-spin" /> : <Plus size={14} />}
</label>
</div>
<div>
<p className="font-bold text-slate-900 text-lg">{b.name}</p>
<p className="text-sm text-slate-600">
{b.specialties.length > 0 ? b.specialties.join(', ') : 'Sem especialidades'}
</p>
</div>
<input
type="text"
placeholder="Pesquisar"
value={barberSearchQuery}
onChange={(e) => setBarberSearchQuery(e.target.value)}
className="w-full h-14 pl-12 pr-4 bg-[#0d0d0d] border border-white/5 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 transition-all font-medium"
/>
</div>
{/* Grelha de Profissionais */}
<div className="grid md:grid-cols-2 gap-4">
{shop.barbers
.filter(b => b.name.toLowerCase().includes(barberSearchQuery.toLowerCase()))
.map((b) => (
<div key={b.id} className="group relative flex items-center gap-4 p-4 bg-[#0d0d0d] border border-white/5 rounded-[2rem] hover:border-indigo-500/30 transition-all duration-300 shadow-xl">
{/* Avatar squircle robusto */}
<div className="relative">
<div className="w-24 h-24 rounded-[1.5rem] overflow-hidden border-2 border-white/5 bg-slate-900 flex items-center justify-center transition-transform group-hover:scale-105 duration-500">
{b.imageUrl ? (
<img src={b.imageUrl} alt={b.name} className="w-full h-full object-cover" />
) : (
<Users size={32} className="text-slate-700" />
)}
</div>
<Button variant="danger" size="sm" onClick={() => deleteBarber(shop.id, b.id)}>
{/* Botão de Upload Escondido */}
<input
type="file"
id={`barber-img-${b.id}`}
className="hidden"
accept="image/*"
onChange={(e) => handleBarberImageUpload(b.id, e)}
/>
<label
htmlFor={`barber-img-${b.id}`}
className="absolute inset-0 bg-black/40 text-white flex items-center justify-center rounded-[1.5rem] opacity-0 group-hover:opacity-100 cursor-pointer transition-opacity"
>
{uploadingBarberId === b.id ? <RefreshCw size={20} className="animate-spin" /> : <Plus size={20} />}
</label>
</div>
{/* Info Profissional */}
<div className="flex-1">
<h3 className="text-xl font-bold text-white tracking-tight leading-tight">{b.name}</h3>
{/* O utilizador pediu para ignorar observações/especialidades, removido para o look "mais limpo" do screenshot */}
</div>
{/* Ações Rápidas */}
<div className="flex flex-col gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<Button
variant="danger"
size="sm"
className="rounded-xl h-10 w-10 p-0 flex items-center justify-center bg-transparent border-white/10 hover:bg-red-500/20"
onClick={() => deleteBarber(shop.id, b.id)}
>
<Trash2 size={16} />
</Button>
</div>
</div>
))}
{shop.barbers.length === 0 && (
<div className="text-center py-12">
<Users size={48} className="mx-auto text-slate-300 mb-3" />
<p className="text-slate-600 font-medium">Nenhum barbeiro registado</p>
</div>
)}
</div>
<div className="border-t border-slate-200 pt-4">
<h3 className="text-base font-semibold text-slate-900 mb-3">Adicionar novo barbeiro</h3>
<div className="grid md:grid-cols-3 gap-3">
{shop.barbers.filter(b => b.name.toLowerCase().includes(barberSearchQuery.toLowerCase())).length === 0 && (
<div className="col-span-full py-12 text-center bg-[#0d0d0d] border border-white/5 rounded-[2rem]">
<Users size={48} className="mx-auto text-slate-800 mb-3" />
<p className="text-slate-500 font-medium italic">Nenhum profissional encontrado com esse nome.</p>
</div>
)}
</div>
{/* Formulário de Adição (Mantido mas modernizado) */}
<div className="pt-8 mt-4 border-t border-slate-200">
<h3 className="text-base font-semibold text-slate-900 mb-4">Novo Profissional</h3>
<div className="grid md:grid-cols-3 gap-3">
<div className="md:col-span-2">
<Input
label="Nome"
label="Nome do Profissional"
placeholder="Ex: João Silva"
value={barberName}
onChange={(e) => setBarberName(e.target.value)}
/>
<Input
label="Especialidades"
placeholder="Fade, Navalha, Barba"
value={barberSpecs}
onChange={(e) => setBarberSpecs(e.target.value)}
/>
<div className="flex items-end">
<Button onClick={addNewBarber} className="w-full">
<PlusIcon size={16} className="mr-2" />
Adicionar
</Button>
</div>
</div>
<div className="flex items-end">
<Button onClick={addNewBarber} className="w-full bg-indigo-600 hover:bg-indigo-700 h-11">
<PlusIcon size={16} className="mr-2" />
Adicionar
</Button>
</div>
</div>
</Card>
</div>
</div>
)}