26.1.28
This commit is contained in:
@@ -174,11 +174,11 @@ const AlunoHome = memo(() => {
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
<View style={styles.topBar}>
|
||||
<TouchableOpacity onPress={() => router.push('/perfil')}>
|
||||
<TouchableOpacity onPress={() => router.push('/Aluno/perfil')}>
|
||||
<Ionicons name="person-circle-outline" size={32} color={themeStyles.texto} />
|
||||
</TouchableOpacity>
|
||||
<Text style={[styles.title, { color: themeStyles.texto }]}>Estágios+</Text>
|
||||
<TouchableOpacity onPress={() => router.push('/definicoes')}>
|
||||
<TouchableOpacity onPress={() => router.push('/Aluno/definicoes')}>
|
||||
<Ionicons name="settings-outline" size={26} color={themeStyles.texto} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
@@ -2,22 +2,21 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
Modal,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import { useTheme } from '../../../themecontext'; // <- seu contexto de tema
|
||||
import { useTheme } from '../../../themecontext';
|
||||
|
||||
interface Falta {
|
||||
dia: string;
|
||||
motivo?: string;
|
||||
pdfUrl?: string;
|
||||
}
|
||||
|
||||
@@ -33,16 +32,22 @@ const alunosData: Aluno[] = [
|
||||
id: 1,
|
||||
nome: 'João Silva',
|
||||
faltas: [
|
||||
{ dia: '2026-01-20', motivo: 'Doença', pdfUrl: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf' },
|
||||
{ dia: '2026-01-22', motivo: 'Atraso' },
|
||||
{
|
||||
dia: '2026-01-20',
|
||||
pdfUrl: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
|
||||
},
|
||||
{ dia: '2026-01-22' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
nome: 'Maria Fernandes',
|
||||
faltas: [
|
||||
{ dia: '2026-01-21', motivo: 'Consulta médica' },
|
||||
{ dia: '2026-01-23', motivo: 'Doença', pdfUrl: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf' },
|
||||
{ dia: '2026-01-21' },
|
||||
{
|
||||
dia: '2026-01-23',
|
||||
pdfUrl: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -82,18 +87,22 @@ export default function FaltasAlunos() {
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
{/* Top Bar */}
|
||||
<View style={styles.topBar}>
|
||||
<TouchableOpacity onPress={() => {
|
||||
if (alunoSelecionado) {
|
||||
setAlunoSelecionado(null); // voltar para lista de alunos
|
||||
} else {
|
||||
router.back(); // voltar para menu
|
||||
}
|
||||
}}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (alunoSelecionado) {
|
||||
setAlunoSelecionado(null);
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Ionicons name="arrow-back-outline" size={26} color={cores.azul} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text style={[styles.topTitle, { color: cores.texto }]}>
|
||||
{!alunoSelecionado ? 'Faltas dos Alunos' : alunoSelecionado.nome}
|
||||
</Text>
|
||||
|
||||
<View style={{ width: 26 }} />
|
||||
</View>
|
||||
|
||||
@@ -107,8 +116,14 @@ export default function FaltasAlunos() {
|
||||
onPress={() => setAlunoSelecionado(aluno)}
|
||||
>
|
||||
<Ionicons name="person-outline" size={28} color={cores.azul} />
|
||||
<Text style={[styles.alunoName, { color: cores.texto }]}>{aluno.nome}</Text>
|
||||
<Ionicons name="chevron-forward-outline" size={20} color={cores.textoSecundario} />
|
||||
<Text style={[styles.alunoName, { color: cores.texto }]}>
|
||||
{aluno.nome}
|
||||
</Text>
|
||||
<Ionicons
|
||||
name="chevron-forward-outline"
|
||||
size={20}
|
||||
color={cores.textoSecundario}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
@@ -118,17 +133,38 @@ export default function FaltasAlunos() {
|
||||
{alunoSelecionado && (
|
||||
<View style={{ marginTop: 10 }}>
|
||||
{alunoSelecionado.faltas.map((falta, idx) => (
|
||||
<View key={idx} style={[styles.faltaCard, { backgroundColor: cores.card }]}>
|
||||
<View
|
||||
key={idx}
|
||||
style={[styles.faltaCard, { backgroundColor: cores.card }]}
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={[styles.dia, { color: cores.azul }]}>{falta.dia}</Text>
|
||||
<Text style={[styles.motivo, { color: cores.texto }]}>{falta.motivo || 'Sem motivo'}</Text>
|
||||
<Text style={[styles.status, { color: falta.pdfUrl ? cores.verde : cores.vermelho }]}>
|
||||
{falta.pdfUrl ? 'Justificada com PDF' : 'Não justificada'}
|
||||
<Text style={[styles.dia, { color: cores.azul }]}>
|
||||
{falta.dia}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
style={[
|
||||
styles.status,
|
||||
{
|
||||
color: falta.pdfUrl
|
||||
? cores.verde
|
||||
: cores.vermelho,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{falta.pdfUrl
|
||||
? 'Falta justificada'
|
||||
: 'Falta não justificada'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{falta.pdfUrl && (
|
||||
<TouchableOpacity onPress={() => verPdf(falta)}>
|
||||
<Ionicons name="document-text-outline" size={24} color={cores.azul} />
|
||||
<Ionicons
|
||||
name="document-text-outline"
|
||||
size={24}
|
||||
color={cores.azul}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
@@ -140,13 +176,21 @@ export default function FaltasAlunos() {
|
||||
{/* Modal PDF */}
|
||||
<Modal visible={pdfModalVisible} animationType="slide">
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: cores.fundo }}>
|
||||
<View style={[styles.pdfHeader, { borderBottomColor: cores.textoSecundario }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.pdfHeader,
|
||||
{ borderBottomColor: cores.textoSecundario },
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity onPress={() => setPdfModalVisible(false)}>
|
||||
<Ionicons name="close-outline" size={28} color={cores.azul} />
|
||||
</TouchableOpacity>
|
||||
<Text style={[styles.pdfTitle, { color: cores.texto }]}>Visualizador de PDF</Text>
|
||||
<Text style={[styles.pdfTitle, { color: cores.texto }]}>
|
||||
Visualizador de PDF
|
||||
</Text>
|
||||
<View style={{ width: 28 }} />
|
||||
</View>
|
||||
|
||||
{pdfUrl && <WebView source={{ uri: pdfUrl }} style={{ flex: 1 }} />}
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
@@ -155,10 +199,23 @@ export default function FaltasAlunos() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safe: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 },
|
||||
safe: {
|
||||
flex: 1,
|
||||
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0,
|
||||
},
|
||||
container: { padding: 20, paddingBottom: 40 },
|
||||
topBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 },
|
||||
topTitle: { fontSize: 20, fontWeight: '700', textAlign: 'center', flex: 1 },
|
||||
topBar: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 16,
|
||||
},
|
||||
topTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '700',
|
||||
textAlign: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
alunoCard: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -171,7 +228,12 @@ const styles = StyleSheet.create({
|
||||
shadowRadius: 4,
|
||||
elevation: 2,
|
||||
},
|
||||
alunoName: { fontSize: 16, fontWeight: '600', marginLeft: 12, flex: 1 },
|
||||
alunoName: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginLeft: 12,
|
||||
flex: 1,
|
||||
},
|
||||
faltaCard: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -185,9 +247,15 @@ const styles = StyleSheet.create({
|
||||
shadowRadius: 4,
|
||||
elevation: 2,
|
||||
},
|
||||
dia: { fontSize: 14, fontWeight: '700', marginBottom: 4 },
|
||||
motivo: { fontSize: 14, marginBottom: 4 },
|
||||
status: { fontSize: 13, fontWeight: '600' },
|
||||
dia: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
marginBottom: 6,
|
||||
},
|
||||
status: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
},
|
||||
pdfHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -195,5 +263,8 @@ const styles = StyleSheet.create({
|
||||
padding: 16,
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
pdfTitle: { fontSize: 18, fontWeight: '700' },
|
||||
pdfTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { memo, useMemo, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
@@ -14,22 +15,38 @@ import {
|
||||
View
|
||||
} from 'react-native';
|
||||
import { useTheme } from '../../../themecontext';
|
||||
import type { Empresa } from './ListaEmpresas';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
|
||||
// Interface atualizada para incluir a lista de alunos
|
||||
export interface Empresa {
|
||||
id: number;
|
||||
nome: string;
|
||||
morada: string;
|
||||
tutor_nome: string;
|
||||
tutor_telefone: string;
|
||||
curso: string;
|
||||
alunos?: string[]; // Array com nomes dos alunos
|
||||
}
|
||||
|
||||
const DetalhesEmpresa = memo(() => {
|
||||
const { isDarkMode } = useTheme();
|
||||
const router = useRouter();
|
||||
const params = useLocalSearchParams();
|
||||
|
||||
// Parse da empresa recebida
|
||||
// Parse seguro dos dados vindos da navegação
|
||||
const empresaOriginal: Empresa = useMemo(() => {
|
||||
if (!params.empresa) return null as any;
|
||||
if (!params.empresa) return {} as Empresa;
|
||||
const str = Array.isArray(params.empresa) ? params.empresa[0] : params.empresa;
|
||||
return JSON.parse(str);
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch {
|
||||
return {} as Empresa;
|
||||
}
|
||||
}, [params.empresa]);
|
||||
|
||||
const [empresaLocal, setEmpresaLocal] = useState<Empresa>({ ...empresaOriginal });
|
||||
const [editando, setEditando] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const cores = {
|
||||
fundo: isDarkMode ? '#121212' : '#f1f3f5',
|
||||
@@ -41,115 +58,150 @@ const DetalhesEmpresa = memo(() => {
|
||||
vermelho: '#dc3545',
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
setEditando(false);
|
||||
Alert.alert('Sucesso', 'Empresa atualizada!');
|
||||
// Aqui depois substituis por update no Supabase
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { error } = await supabase
|
||||
.from('empresas')
|
||||
.update({
|
||||
nome: empresaLocal.nome,
|
||||
morada: empresaLocal.morada,
|
||||
tutor_nome: empresaLocal.tutor_nome,
|
||||
tutor_telefone: empresaLocal.tutor_telefone,
|
||||
curso: empresaLocal.curso,
|
||||
})
|
||||
.eq('id', empresaLocal.id);
|
||||
|
||||
if (error) throw error;
|
||||
setEditando(false);
|
||||
Alert.alert('Sucesso', 'Dados atualizados!');
|
||||
} catch (error: any) {
|
||||
Alert.alert('Erro', error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
Alert.alert(
|
||||
'Apagar Empresa',
|
||||
'Tem a certeza que deseja apagar esta empresa?',
|
||||
[
|
||||
{ text: 'Cancelar', style: 'cancel' },
|
||||
{ text: 'Apagar', style: 'destructive', onPress: () => router.back() }
|
||||
]
|
||||
);
|
||||
Alert.alert('Apagar', `Apagar ${empresaLocal.nome}?`, [
|
||||
{ text: 'Cancelar', style: 'cancel' },
|
||||
{
|
||||
text: 'Apagar',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
await supabase.from('empresas').delete().eq('id', empresaLocal.id);
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
<View style={{ flex: 1, backgroundColor: cores.fundo }}>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={cores.fundo} translucent={false} />
|
||||
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity style={[styles.btnVoltar, { backgroundColor: cores.card }]} onPress={() => router.back()}>
|
||||
<Ionicons name="arrow-back" size={24} color={cores.texto} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text style={[styles.tituloGeral, { color: cores.texto }]}>{empresaLocal.nome}</Text>
|
||||
|
||||
<View style={{ flexDirection: 'row', gap: 8 }}>
|
||||
<TouchableOpacity style={[styles.btnAcao, { backgroundColor: '#ffc107' }]} onPress={() => setEditando(!editando)}>
|
||||
<Ionicons name="pencil" size={20} color="#000" />
|
||||
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity style={[styles.btnVoltar, { backgroundColor: cores.card }]} onPress={() => router.back()}>
|
||||
<Ionicons name="arrow-back" size={24} color={cores.texto} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={[styles.btnAcao, { backgroundColor: cores.vermelho }]} onPress={handleDelete}>
|
||||
<Ionicons name="trash" size={20} color="#fff" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
{/* Dados da Empresa */}
|
||||
<View style={[styles.card, { backgroundColor: cores.card }]}>
|
||||
<Text style={[styles.tituloCard, { color: cores.azul }]}>Dados da Empresa</Text>
|
||||
|
||||
{['nome', 'curso', 'morada', 'tutor', 'telefone'].map((campo) => (
|
||||
<View key={campo}>
|
||||
<Text style={[styles.label, { color: cores.textoSecundario }]}>
|
||||
{campo.charAt(0).toUpperCase() + campo.slice(1)}
|
||||
</Text>
|
||||
{editando ? (
|
||||
<TextInput
|
||||
style={[styles.input, { color: cores.texto, borderColor: cores.textoSecundario }]}
|
||||
value={(empresaLocal as any)[campo]}
|
||||
onChangeText={(v) => setEmpresaLocal({ ...empresaLocal, [campo]: v })}
|
||||
/>
|
||||
) : (
|
||||
<Text style={[styles.valor, { color: cores.texto }]}>
|
||||
{(empresaLocal as any)[campo]}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* Botão Guardar alterações */}
|
||||
{editando && (
|
||||
<TouchableOpacity style={[styles.saveButton, { backgroundColor: cores.azul }]} onPress={handleSave}>
|
||||
<Text style={{ color: '#fff', fontWeight: 'bold', textAlign: 'center' }}>Guardar alterações</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* Estatísticas */}
|
||||
<View style={[styles.card, { backgroundColor: cores.card }]}>
|
||||
<Text style={[styles.tituloCard, { color: cores.azul }]}>Estatísticas</Text>
|
||||
<Text style={[styles.label, { color: cores.textoSecundario }]}>Total de Alunos</Text>
|
||||
<Text style={[styles.valor, { color: cores.verde, fontSize: 18 }]}>
|
||||
{empresaLocal.alunos?.length || 0}
|
||||
<Text style={[styles.tituloGeral, { color: cores.texto }]} numberOfLines={1}>
|
||||
{empresaLocal.nome || 'Detalhes'}
|
||||
</Text>
|
||||
|
||||
<View style={styles.headerAcoes}>
|
||||
<TouchableOpacity
|
||||
style={[styles.btnAcao, { backgroundColor: editando ? cores.vermelho : '#ffc107' }]}
|
||||
onPress={() => setEditando(!editando)}
|
||||
>
|
||||
<Ionicons name={editando ? "close" : "pencil"} size={20} color={editando ? "#fff" : "#000"} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={[styles.btnAcao, { backgroundColor: cores.vermelho }]} onPress={handleDelete}>
|
||||
<Ionicons name="trash" size={20} color="#fff" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Lista de Alunos */}
|
||||
{empresaLocal.alunos && empresaLocal.alunos.length > 0 && (
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
{/* CARD DE DADOS */}
|
||||
<View style={[styles.card, { backgroundColor: cores.card }]}>
|
||||
<Text style={[styles.tituloCard, { color: cores.azul }]}>Alunos na Empresa</Text>
|
||||
{empresaLocal.alunos.map((aluno, index) => (
|
||||
<Text key={index} style={[styles.valor, { color: cores.texto, marginVertical: 2 }]}>
|
||||
{aluno}
|
||||
</Text>
|
||||
<Text style={[styles.tituloCard, { color: cores.azul }]}>Informações da Empresa</Text>
|
||||
{[
|
||||
{ label: 'Nome', key: 'nome' },
|
||||
{ label: 'Curso', key: 'curso' },
|
||||
{ label: 'Morada', key: 'morada' },
|
||||
{ label: 'Tutor', key: 'tutor_nome' },
|
||||
{ label: 'Telefone', key: 'tutor_telefone' },
|
||||
].map((item) => (
|
||||
<View key={item.key} style={styles.campoWrapper}>
|
||||
<Text style={[styles.label, { color: cores.textoSecundario }]}>{item.label}</Text>
|
||||
{editando ? (
|
||||
<TextInput
|
||||
style={[styles.input, { color: cores.texto, borderColor: cores.textoSecundario }]}
|
||||
value={(empresaLocal as any)[item.key]}
|
||||
onChangeText={(v) => setEmpresaLocal(prev => ({ ...prev, [item.key]: v }))}
|
||||
/>
|
||||
) : (
|
||||
<Text style={[styles.valor, { color: cores.texto }]}>{(empresaLocal as any)[item.key] || '---'}</Text>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
{editando && (
|
||||
<TouchableOpacity style={[styles.saveButton, { backgroundColor: cores.azul }]} onPress={handleSave} disabled={loading}>
|
||||
{loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.txtBtn}>Confirmar Alterações</Text>}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* ESTATÍSTICAS COM LISTA DE ALUNOS */}
|
||||
<View style={[styles.card, { backgroundColor: cores.card }]}>
|
||||
<Text style={[styles.tituloCard, { color: cores.azul }]}>Alunos em Estágio</Text>
|
||||
|
||||
{empresaLocal.alunos && empresaLocal.alunos.length > 0 ? (
|
||||
empresaLocal.alunos.map((aluno, index) => (
|
||||
<View key={index} style={styles.alunoRow}>
|
||||
<Ionicons name="person" size={16} color={cores.azul} style={{ marginRight: 8 }} />
|
||||
<Text style={[styles.valor, { color: cores.texto }]}>{aluno}</Text>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<Text style={[styles.valor, { color: cores.textoSecundario, textAlign: 'center' }]}>
|
||||
Nenhum aluno associado.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<View style={{ marginTop: 20, borderTopWidth: 1, borderTopColor: '#f0f0f0', paddingTop: 10 }}>
|
||||
<Text style={[styles.label, { color: cores.textoSecundario }]}>Total de Alunos</Text>
|
||||
<Text style={[styles.valor, { color: cores.verde, fontSize: 20 }]}>
|
||||
{empresaLocal.alunos?.length || 0}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
export default DetalhesEmpresa;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safe: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 },
|
||||
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 10 },
|
||||
safe: { flex: 1, paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight || 0) : 0 },
|
||||
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 15, paddingVertical: 10 },
|
||||
headerAcoes: { flexDirection: 'row', gap: 8 },
|
||||
btnVoltar: { width: 40, height: 40, borderRadius: 20, justifyContent: 'center', alignItems: 'center', elevation: 2 },
|
||||
btnAcao: { width: 40, height: 40, borderRadius: 10, justifyContent: 'center', alignItems: 'center' },
|
||||
tituloGeral: { fontSize: 22, fontWeight: 'bold' },
|
||||
container: { padding: 20, gap: 20, paddingBottom: 40 },
|
||||
card: { padding: 20, borderRadius: 16, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4 },
|
||||
tituloCard: { fontSize: 18, fontWeight: 'bold', textAlign: 'center', marginBottom: 10, borderBottomWidth: 1, paddingBottom: 8 },
|
||||
label: { marginTop: 12, fontSize: 13 },
|
||||
valor: { fontSize: 16, fontWeight: '600' },
|
||||
input: { borderWidth: 1, borderRadius: 10, padding: 10, marginTop: 2 },
|
||||
saveButton: { padding: 16, borderRadius: 10, marginTop: 16 },
|
||||
});
|
||||
btnAcao: { width: 38, height: 38, borderRadius: 10, justifyContent: 'center', alignItems: 'center', elevation: 2 },
|
||||
tituloGeral: { fontSize: 18, fontWeight: 'bold', flex: 1, textAlign: 'center', marginHorizontal: 10 },
|
||||
container: { padding: 20, gap: 15 },
|
||||
card: { padding: 20, borderRadius: 16, elevation: 3, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4 },
|
||||
tituloCard: { fontSize: 16, fontWeight: 'bold', marginBottom: 15, textAlign: 'center', borderBottomWidth: 1, borderBottomColor: '#f0f0f0', paddingBottom: 8 },
|
||||
campoWrapper: { marginBottom: 15 },
|
||||
label: { fontSize: 11, fontWeight: '700', textTransform: 'uppercase', marginBottom: 4 },
|
||||
valor: { fontSize: 16, fontWeight: '500' },
|
||||
alunoRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, paddingLeft: 5 },
|
||||
input: { borderWidth: 1, borderRadius: 8, padding: 10, fontSize: 16, marginTop: 2 },
|
||||
saveButton: { padding: 16, borderRadius: 12, alignItems: 'center', justifyContent: 'center', marginBottom: 10 },
|
||||
txtBtn: { color: '#fff', fontWeight: 'bold', fontSize: 16 }
|
||||
});
|
||||
@@ -1,136 +1,321 @@
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { memo, useMemo, useState } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
FlatList, Platform, SafeAreaView, StatusBar,
|
||||
StyleSheet, Text, TextInput, TouchableOpacity, View
|
||||
FlatList,
|
||||
Modal,
|
||||
Platform,
|
||||
RefreshControl,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { useTheme } from '../../../themecontext';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
|
||||
export interface Empresa {
|
||||
id: number;
|
||||
nome: string;
|
||||
morada: string;
|
||||
tutor: string;
|
||||
telefone: string;
|
||||
tutor_nome: string;
|
||||
tutor_telefone: string;
|
||||
curso: string;
|
||||
alunos?: string[];
|
||||
}
|
||||
|
||||
const initialEmpresasData: Empresa[] = [
|
||||
{ id: 1, nome: 'Empresa X', morada: 'Rua das Flores, 12', tutor: 'João Santos', telefone: '912345678', curso: 'Técnico de Informática', alunos: ['João Silva'] },
|
||||
{ id: 2, nome: 'Empresa W', morada: 'Av. Central, 45', tutor: 'Ana Costa', telefone: '912345679', curso: 'Técnico de Design', alunos: ['Maria Fernandes'] },
|
||||
];
|
||||
|
||||
const ListaEmpresasProfessor = memo(() => {
|
||||
const { isDarkMode } = useTheme();
|
||||
const router = useRouter();
|
||||
const [search, setSearch] = useState('');
|
||||
const [empresas, setEmpresas] = useState<Empresa[]>(initialEmpresasData);
|
||||
|
||||
const cores = {
|
||||
const [search, setSearch] = useState('');
|
||||
const [empresas, setEmpresas] = useState<Empresa[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
// MODAL + FORM
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [nome, setNome] = useState('');
|
||||
const [morada, setMorada] = useState('');
|
||||
const [tutorNome, setTutorNome] = useState('');
|
||||
const [tutorTelefone, setTutorTelefone] = useState('');
|
||||
const [curso, setCurso] = useState('');
|
||||
|
||||
const cores = useMemo(() => ({
|
||||
fundo: isDarkMode ? '#121212' : '#f1f3f5',
|
||||
card: isDarkMode ? '#1e1e1e' : '#fff',
|
||||
texto: isDarkMode ? '#fff' : '#000',
|
||||
textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d',
|
||||
azul: '#0d6efd',
|
||||
}), [isDarkMode]);
|
||||
|
||||
const fetchEmpresas = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data, error } = await supabase
|
||||
.from('empresas')
|
||||
.select('*')
|
||||
.order('nome', { ascending: true });
|
||||
|
||||
if (error) throw error;
|
||||
setEmpresas(data || []);
|
||||
} catch (error: any) {
|
||||
Alert.alert('Erro ao carregar', error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Filtrar empresas
|
||||
useEffect(() => {
|
||||
fetchEmpresas();
|
||||
}, []);
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true);
|
||||
fetchEmpresas();
|
||||
}, []);
|
||||
|
||||
const filteredEmpresas = useMemo(
|
||||
() => empresas.filter(e => e.nome.toLowerCase().includes(search.toLowerCase())),
|
||||
() => empresas.filter(e =>
|
||||
e.nome?.toLowerCase().includes(search.toLowerCase())
|
||||
),
|
||||
[search, empresas]
|
||||
);
|
||||
|
||||
// Criar nova empresa
|
||||
const criarEmpresa = () => {
|
||||
Alert.prompt(
|
||||
'Nova Empresa',
|
||||
'Insira o nome da empresa',
|
||||
(nome) => {
|
||||
if (!nome) return;
|
||||
const nova: Empresa = {
|
||||
id: Date.now(),
|
||||
nome,
|
||||
morada: 'Não definida',
|
||||
tutor: 'Não definido',
|
||||
telefone: '000000000',
|
||||
curso: 'Não definido',
|
||||
alunos: []
|
||||
};
|
||||
setEmpresas(prev => [nova, ...prev]);
|
||||
router.push({
|
||||
pathname: '/Professor/Empresas/DetalhesEmpresa',
|
||||
params: { empresa: JSON.stringify(nova) }
|
||||
});
|
||||
}
|
||||
);
|
||||
// 👉 CRIAR EMPRESA (AGORA COM TODOS OS CAMPOS)
|
||||
const criarEmpresa = async () => {
|
||||
if (!nome || !morada || !tutorNome || !tutorTelefone || !curso) {
|
||||
Alert.alert('Erro', 'Preenche todos os campos.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('empresas')
|
||||
.insert([{
|
||||
nome: nome.trim(),
|
||||
morada: morada.trim(),
|
||||
tutor_nome: tutorNome.trim(),
|
||||
tutor_telefone: tutorTelefone.trim(),
|
||||
curso: curso.trim(),
|
||||
}])
|
||||
.select();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
setEmpresas(prev => [data![0], ...prev]);
|
||||
|
||||
// limpar form
|
||||
setNome('');
|
||||
setMorada('');
|
||||
setTutorNome('');
|
||||
setTutorTelefone('');
|
||||
setCurso('');
|
||||
|
||||
setModalVisible(false);
|
||||
Alert.alert('Sucesso', 'Empresa criada com sucesso!');
|
||||
} catch (error: any) {
|
||||
Alert.alert('Erro ao criar', error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
|
||||
{/* Header com botão voltar */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity style={[styles.btnVoltar, { backgroundColor: cores.card }]} onPress={() => router.back()}>
|
||||
<Ionicons name="arrow-back" size={24} color={cores.texto} />
|
||||
</TouchableOpacity>
|
||||
<Text style={[styles.tituloGeral, { color: cores.texto }]}>Empresas</Text>
|
||||
<View style={styles.spacer} />
|
||||
</View>
|
||||
|
||||
{/* Input de pesquisa */}
|
||||
<TextInput
|
||||
style={[styles.search, { backgroundColor: cores.card, color: cores.texto }]}
|
||||
placeholder="Pesquisar empresa..."
|
||||
placeholderTextColor={cores.textoSecundario}
|
||||
value={search}
|
||||
onChangeText={setSearch}
|
||||
<View style={{ flex: 1, backgroundColor: cores.fundo }}>
|
||||
<StatusBar
|
||||
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
|
||||
backgroundColor={cores.fundo}
|
||||
/>
|
||||
|
||||
{/* Botão criar empresa */}
|
||||
<TouchableOpacity style={[styles.btnCriar, { backgroundColor: cores.azul }]} onPress={criarEmpresa}>
|
||||
<Text style={{ color: '#fff', fontWeight: 'bold' }}>+ Criar Empresa</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Lista de empresas */}
|
||||
<FlatList
|
||||
data={filteredEmpresas}
|
||||
keyExtractor={item => item.id.toString()}
|
||||
renderItem={({ item }) => (
|
||||
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
|
||||
{/* HEADER */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity
|
||||
style={[styles.card, { backgroundColor: cores.card }]}
|
||||
onPress={() =>
|
||||
router.push({
|
||||
pathname: '/Professor/Empresas/DetalhesEmpresa',
|
||||
params: { empresa: JSON.stringify(item) }
|
||||
})
|
||||
}
|
||||
style={[styles.btnVoltar, { backgroundColor: cores.card }]}
|
||||
onPress={() => router.back()}
|
||||
>
|
||||
<Text style={[styles.nomeEmpresa, { color: cores.azul }]}>{item.nome}</Text>
|
||||
<Text style={[styles.curso, { color: cores.textoSecundario }]}>{item.curso}</Text>
|
||||
<Text style={[styles.tutor, { color: cores.textoSecundario }]}>{item.tutor}</Text>
|
||||
<Ionicons name="arrow-back" size={24} color={cores.texto} />
|
||||
</TouchableOpacity>
|
||||
<Text style={[styles.tituloGeral, { color: cores.texto }]}>
|
||||
Empresas
|
||||
</Text>
|
||||
<View style={{ width: 40 }} />
|
||||
</View>
|
||||
|
||||
{/* SEARCH */}
|
||||
<View style={styles.searchContainer}>
|
||||
<Ionicons name="search" size={20} color={cores.textoSecundario} style={styles.searchIcon} />
|
||||
<TextInput
|
||||
style={[styles.search, { backgroundColor: cores.card, color: cores.texto }]}
|
||||
placeholder="Pesquisar empresa..."
|
||||
placeholderTextColor={cores.textoSecundario}
|
||||
value={search}
|
||||
onChangeText={setSearch}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* BOTÃO NOVA EMPRESA */}
|
||||
<TouchableOpacity
|
||||
style={[styles.btnCriar, { backgroundColor: cores.azul }]}
|
||||
onPress={() => setModalVisible(true)}
|
||||
>
|
||||
<Ionicons name="add-circle-outline" size={20} color="#fff" />
|
||||
<Text style={styles.txtBtnCriar}>Nova Empresa</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* LISTA */}
|
||||
{loading && !refreshing ? (
|
||||
<ActivityIndicator size="large" color={cores.azul} style={{ marginTop: 40 }} />
|
||||
) : (
|
||||
<FlatList
|
||||
data={filteredEmpresas}
|
||||
keyExtractor={item => item.id.toString()}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={cores.azul} />
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
style={[styles.card, { backgroundColor: cores.card }]}
|
||||
onPress={() =>
|
||||
router.push({
|
||||
pathname: '/Professor/Empresas/DetalhesEmpresa',
|
||||
params: { empresa: JSON.stringify(item) }
|
||||
})
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<Text style={[styles.nomeEmpresa, { color: cores.azul }]}>{item.nome}</Text>
|
||||
<Text style={[styles.info, { color: cores.textoSecundario }]}>
|
||||
<Ionicons name="book-outline" size={14} /> {item.curso}
|
||||
</Text>
|
||||
<Text style={[styles.info, { color: cores.textoSecundario }]}>
|
||||
<Ionicons name="person-outline" size={14} /> {item.tutor_nome}
|
||||
</Text>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color={cores.textoSecundario} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
contentContainerStyle={styles.listContent}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
</SafeAreaView>
|
||||
|
||||
{/* MODAL */}
|
||||
<Modal visible={modalVisible} animationType="slide" transparent>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={[styles.modalContent, { backgroundColor: cores.card }]}>
|
||||
<Text style={[styles.modalTitle, { color: cores.texto }]}>
|
||||
Nova Empresa
|
||||
</Text>
|
||||
|
||||
<ScrollView>
|
||||
<Input label="Nome" value={nome} onChangeText={setNome} cores={cores} />
|
||||
<Input label="Morada" value={morada} onChangeText={setMorada} cores={cores} />
|
||||
<Input label="Curso" value={curso} onChangeText={setCurso} cores={cores} />
|
||||
<Input label="Tutor" value={tutorNome} onChangeText={setTutorNome} cores={cores} />
|
||||
<Input
|
||||
label="Telefone"
|
||||
value={tutorTelefone}
|
||||
onChangeText={setTutorTelefone}
|
||||
keyboardType="phone-pad"
|
||||
cores={cores}
|
||||
/>
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
||||
<Text style={{ color: '#dc3545', fontWeight: 'bold' }}>
|
||||
Cancelar
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.btnConfirmar, { backgroundColor: cores.azul }]}
|
||||
onPress={criarEmpresa}
|
||||
>
|
||||
<Text style={{ color: '#fff', fontWeight: 'bold' }}>
|
||||
Criar
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
export default ListaEmpresasProfessor;
|
||||
|
||||
/* INPUT */
|
||||
const Input = ({ label, cores, ...props }: any) => (
|
||||
<View style={{ marginBottom: 10 }}>
|
||||
<Text style={{ color: cores.textoSecundario, marginBottom: 4 }}>
|
||||
{label}
|
||||
</Text>
|
||||
<TextInput
|
||||
{...props}
|
||||
style={{
|
||||
backgroundColor: cores.fundo,
|
||||
color: cores.texto,
|
||||
padding: 12,
|
||||
borderRadius: 10
|
||||
}}
|
||||
placeholderTextColor={cores.textoSecundario}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safe: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 },
|
||||
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 10 },
|
||||
btnVoltar: { width: 40, height: 40, borderRadius: 20, justifyContent: 'center', alignItems: 'center', elevation: 2 },
|
||||
safe: { flex: 1, paddingTop: Platform.OS === 'android' ? 10 : 0 },
|
||||
header: { flexDirection: 'row', justifyContent: 'space-between', padding: 20 },
|
||||
btnVoltar: { width: 40, height: 40, borderRadius: 20, alignItems: 'center', justifyContent: 'center' },
|
||||
tituloGeral: { fontSize: 22, fontWeight: 'bold' },
|
||||
spacer: { width: 40 },
|
||||
search: { borderRadius: 10, padding: 10, margin: 10 },
|
||||
btnCriar: { marginHorizontal: 10, padding: 12, borderRadius: 10, alignItems: 'center', marginBottom: 10 },
|
||||
card: { borderRadius: 10, padding: 15, marginBottom: 10, elevation: 2 },
|
||||
searchContainer: { marginHorizontal: 15, marginBottom: 10 },
|
||||
searchIcon: { position: 'absolute', left: 15, top: 14 },
|
||||
search: { borderRadius: 12, padding: 12, paddingLeft: 45 },
|
||||
btnCriar: { flexDirection: 'row', margin: 15, padding: 15, borderRadius: 12, justifyContent: 'center', gap: 8 },
|
||||
txtBtnCriar: { color: '#fff', fontWeight: 'bold' },
|
||||
listContent: { padding: 15 },
|
||||
card: { flexDirection: 'row', justifyContent: 'space-between', padding: 18, borderRadius: 15, marginBottom: 12 },
|
||||
nomeEmpresa: { fontSize: 18, fontWeight: 'bold' },
|
||||
curso: { fontSize: 14, marginTop: 2 },
|
||||
tutor: { fontSize: 14, marginTop: 2 },
|
||||
info: { fontSize: 14, marginTop: 3 },
|
||||
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
justifyContent: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
modalContent: {
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
maxHeight: '90%',
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 15,
|
||||
textAlign: 'center',
|
||||
},
|
||||
modalButtons: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 15,
|
||||
},
|
||||
btnConfirmar: {
|
||||
padding: 14,
|
||||
borderRadius: 10,
|
||||
minWidth: 100,
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,25 +1,79 @@
|
||||
// app/index.tsx
|
||||
import { useRouter } from 'expo-router';
|
||||
import { KeyboardAvoidingView, Platform, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import {
|
||||
Alert,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Auth from '../components/Auth';
|
||||
import { supabase } from './lib/supabase';
|
||||
|
||||
export default function LoginScreen() {
|
||||
const router = useRouter();
|
||||
|
||||
const handleLoginSuccess = () => {
|
||||
router.replace('/Professor/ProfessorHome');
|
||||
const handleLoginSuccess = async () => {
|
||||
try {
|
||||
// 1️⃣ buscar utilizador autenticado
|
||||
const {
|
||||
data: { user },
|
||||
error: userError,
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (userError || !user) {
|
||||
Alert.alert('Erro', 'Utilizador não autenticado');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2️⃣ buscar tipo (professor / aluno)
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.select('tipo')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
|
||||
if (error || !data) {
|
||||
Alert.alert(
|
||||
'Erro',
|
||||
'Não foi possível obter o tipo de utilizador'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3️⃣ redirecionar conforme o tipo
|
||||
if (data.tipo === 'professor') {
|
||||
router.replace('/Professor/ProfessorHome');
|
||||
} else if (data.tipo === 'aluno') {
|
||||
router.replace('/Aluno/AlunoHome');
|
||||
} else {
|
||||
Alert.alert('Erro', 'Tipo de utilizador inválido');
|
||||
}
|
||||
} catch (err) {
|
||||
Alert.alert('Erro', 'Erro inesperado no login');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView style={{ flex: 1, backgroundColor: '#f8f9fa' }} behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
|
||||
<ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled">
|
||||
<KeyboardAvoidingView
|
||||
style={{ flex: 1, backgroundColor: '#f8f9fa' }}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContainer}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<View style={styles.content}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>📱 Estágios+</Text>
|
||||
<Text style={styles.subtitle}>Escola Profissional de Vila do Conde</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
Escola Profissional de Vila do Conde
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Componente Auth */}
|
||||
{/* COMPONENTE DE LOGIN */}
|
||||
<Auth onLoginSuccess={handleLoginSuccess} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
@@ -28,9 +82,29 @@ export default function LoginScreen() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollContainer: { flexGrow: 1, justifyContent: 'center', paddingHorizontal: 24, paddingVertical: 40 },
|
||||
content: { flex: 1, justifyContent: 'center' },
|
||||
header: { alignItems: 'center', marginBottom: 48 },
|
||||
title: { fontSize: 32, fontWeight: '800', color: '#2d3436', marginBottom: 8 },
|
||||
subtitle: { fontSize: 16, color: '#636e72', textAlign: 'center' },
|
||||
scrollContainer: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 24,
|
||||
paddingVertical: 40,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
header: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 48,
|
||||
},
|
||||
title: {
|
||||
fontSize: 32,
|
||||
fontWeight: '800',
|
||||
color: '#2d3436',
|
||||
marginBottom: 8,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
color: '#636e72',
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user