mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-25 06:14:37 +02:00
style: auto-format codebase with Prettier
Applied formatting to 1487+ files using pnpm format:write - TypeScript/JavaScript files - Svelte components - Astro pages - JSON configs - Markdown docs 13 files still need manual review (Astro JSX comments)
This commit is contained in:
parent
0241f5554c
commit
d36b321d9d
3952 changed files with 661498 additions and 739751 deletions
|
|
@ -4,242 +4,236 @@ import { supabase } from '../utils/supabase';
|
|||
import { Session } from '@supabase/supabase-js';
|
||||
|
||||
interface Profile {
|
||||
id: string;
|
||||
first_name: string | null;
|
||||
last_name: string | null;
|
||||
avatar_url: string | null;
|
||||
is_individual: boolean;
|
||||
individual_quota: number;
|
||||
individual_usage: number;
|
||||
id: string;
|
||||
first_name: string | null;
|
||||
last_name: string | null;
|
||||
avatar_url: string | null;
|
||||
is_individual: boolean;
|
||||
individual_quota: number;
|
||||
individual_usage: number;
|
||||
}
|
||||
|
||||
export default function Account({ session }: { session: Session }) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
const [profile, setProfile] = useState<Profile | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
const [profile, setProfile] = useState<Profile | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (session) getProfile();
|
||||
}, [session]);
|
||||
useEffect(() => {
|
||||
if (session) getProfile();
|
||||
}, [session]);
|
||||
|
||||
async function getProfile() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { user } = session;
|
||||
async function getProfile() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { user } = session;
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setProfile(data);
|
||||
setFirstName(data.first_name || '');
|
||||
setLastName(data.last_name || '');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
Alert.alert('Fehler beim Laden des Profils', error.message);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
if (data) {
|
||||
setProfile(data);
|
||||
setFirstName(data.first_name || '');
|
||||
setLastName(data.last_name || '');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
Alert.alert('Fehler beim Laden des Profils', error.message);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { user } = session;
|
||||
async function updateProfile() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { user } = session;
|
||||
|
||||
// Prüfen, ob das Profil bereits existiert
|
||||
if (!profile) {
|
||||
// Erstelle ein neues Profil, wenn es noch nicht existiert
|
||||
const { error: insertError } = await supabase
|
||||
.from('profiles')
|
||||
.insert([
|
||||
{
|
||||
id: user.id,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
is_individual: true, // Standardmäßig als Einzelnutzer
|
||||
individual_quota: 0,
|
||||
individual_usage: 0,
|
||||
},
|
||||
]);
|
||||
// Prüfen, ob das Profil bereits existiert
|
||||
if (!profile) {
|
||||
// Erstelle ein neues Profil, wenn es noch nicht existiert
|
||||
const { error: insertError } = await supabase.from('profiles').insert([
|
||||
{
|
||||
id: user.id,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
is_individual: true, // Standardmäßig als Einzelnutzer
|
||||
individual_quota: 0,
|
||||
individual_usage: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
if (insertError) throw insertError;
|
||||
} else {
|
||||
// Aktualisiere das bestehende Profil
|
||||
const { error: updateError } = await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
updated_at: new Date(),
|
||||
})
|
||||
.eq('id', user.id);
|
||||
if (insertError) throw insertError;
|
||||
} else {
|
||||
// Aktualisiere das bestehende Profil
|
||||
const { error: updateError } = await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
updated_at: new Date(),
|
||||
})
|
||||
.eq('id', user.id);
|
||||
|
||||
if (updateError) throw updateError;
|
||||
}
|
||||
if (updateError) throw updateError;
|
||||
}
|
||||
|
||||
Alert.alert('Erfolg', 'Profil erfolgreich aktualisiert!');
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
Alert.alert('Fehler beim Aktualisieren des Profils', error.message);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
Alert.alert('Erfolg', 'Profil erfolgreich aktualisiert!');
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
Alert.alert('Fehler beim Aktualisieren des Profils', error.message);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { error } = await supabase.auth.signOut();
|
||||
if (error) throw error;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
Alert.alert('Fehler beim Abmelden', error.message);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
async function signOut() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { error } = await supabase.auth.signOut();
|
||||
if (error) throw error;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
Alert.alert('Fehler beim Abmelden', error.message);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.profileContainer}>
|
||||
<Text style={styles.header}>Mein Profil</Text>
|
||||
|
||||
<View style={styles.formGroup}>
|
||||
<Text style={styles.label}>E-Mail</Text>
|
||||
<Text style={styles.value}>{session?.user?.email}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.formGroup}>
|
||||
<Text style={styles.label}>Vorname</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={firstName}
|
||||
onChangeText={setFirstName}
|
||||
placeholder="Vorname eingeben"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.formGroup}>
|
||||
<Text style={styles.label}>Nachname</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
placeholder="Nachname eingeben"
|
||||
/>
|
||||
</View>
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.profileContainer}>
|
||||
<Text style={styles.header}>Mein Profil</Text>
|
||||
|
||||
{profile && profile.is_individual && (
|
||||
<View style={styles.quotaContainer}>
|
||||
<Text style={styles.label}>Verfügbare Kredite</Text>
|
||||
<Text style={styles.quota}>
|
||||
{profile.individual_quota - profile.individual_usage} / {profile.individual_quota}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.formGroup}>
|
||||
<Text style={styles.label}>E-Mail</Text>
|
||||
<Text style={styles.value}>{session?.user?.email}</Text>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.button}
|
||||
onPress={updateProfile}
|
||||
disabled={loading}
|
||||
>
|
||||
<Text style={styles.buttonText}>
|
||||
{loading ? 'Wird aktualisiert...' : 'Profil aktualisieren'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.formGroup}>
|
||||
<Text style={styles.label}>Vorname</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={firstName}
|
||||
onChangeText={setFirstName}
|
||||
placeholder="Vorname eingeben"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.signOutButton]}
|
||||
onPress={signOut}
|
||||
disabled={loading}
|
||||
>
|
||||
<Text style={styles.buttonText}>Abmelden</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
<View style={styles.formGroup}>
|
||||
<Text style={styles.label}>Nachname</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
placeholder="Nachname eingeben"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{profile && profile.is_individual && (
|
||||
<View style={styles.quotaContainer}>
|
||||
<Text style={styles.label}>Verfügbare Kredite</Text>
|
||||
<Text style={styles.quota}>
|
||||
{profile.individual_quota - profile.individual_usage} / {profile.individual_quota}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<TouchableOpacity style={styles.button} onPress={updateProfile} disabled={loading}>
|
||||
<Text style={styles.buttonText}>
|
||||
{loading ? 'Wird aktualisiert...' : 'Profil aktualisieren'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.signOutButton]}
|
||||
onPress={signOut}
|
||||
disabled={loading}
|
||||
>
|
||||
<Text style={styles.buttonText}>Abmelden</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
},
|
||||
profileContainer: {
|
||||
backgroundColor: 'white',
|
||||
padding: 20,
|
||||
borderRadius: 10,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
header: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 20,
|
||||
},
|
||||
formGroup: {
|
||||
marginBottom: 15,
|
||||
},
|
||||
label: {
|
||||
fontSize: 16,
|
||||
marginBottom: 5,
|
||||
fontWeight: '500',
|
||||
},
|
||||
value: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
paddingVertical: 10,
|
||||
},
|
||||
input: {
|
||||
height: 50,
|
||||
borderWidth: 1,
|
||||
borderColor: '#ddd',
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 10,
|
||||
backgroundColor: '#f9f9f9',
|
||||
},
|
||||
quotaContainer: {
|
||||
marginVertical: 15,
|
||||
padding: 15,
|
||||
backgroundColor: '#f0f8ff',
|
||||
borderRadius: 5,
|
||||
},
|
||||
quota: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#0055FF',
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#0055FF',
|
||||
height: 50,
|
||||
borderRadius: 5,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 15,
|
||||
},
|
||||
buttonText: {
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
signOutButton: {
|
||||
backgroundColor: '#ff3b30',
|
||||
marginTop: 10,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
},
|
||||
profileContainer: {
|
||||
backgroundColor: 'white',
|
||||
padding: 20,
|
||||
borderRadius: 10,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
header: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 20,
|
||||
},
|
||||
formGroup: {
|
||||
marginBottom: 15,
|
||||
},
|
||||
label: {
|
||||
fontSize: 16,
|
||||
marginBottom: 5,
|
||||
fontWeight: '500',
|
||||
},
|
||||
value: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
paddingVertical: 10,
|
||||
},
|
||||
input: {
|
||||
height: 50,
|
||||
borderWidth: 1,
|
||||
borderColor: '#ddd',
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 10,
|
||||
backgroundColor: '#f9f9f9',
|
||||
},
|
||||
quotaContainer: {
|
||||
marginVertical: 15,
|
||||
padding: 15,
|
||||
backgroundColor: '#f0f8ff',
|
||||
borderRadius: 5,
|
||||
},
|
||||
quota: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#0055FF',
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#0055FF',
|
||||
height: 50,
|
||||
borderRadius: 5,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 15,
|
||||
},
|
||||
buttonText: {
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
signOutButton: {
|
||||
backgroundColor: '#ff3b30',
|
||||
marginTop: 10,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,329 +1,372 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Alert, StyleSheet, View, TextInput, TouchableOpacity, Text, Image, Platform } from 'react-native';
|
||||
import {
|
||||
Alert,
|
||||
StyleSheet,
|
||||
View,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
Image,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { useTheme, useThemeColors, lightColors, darkColors } from '../utils/themeContext';
|
||||
|
||||
export default function Auth() {
|
||||
const { isDarkMode } = useTheme();
|
||||
const themeColors = useThemeColors();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isSignUp, setIsSignUp] = useState(false);
|
||||
const [isResetPassword, setIsResetPassword] = useState(false);
|
||||
const { isDarkMode } = useTheme();
|
||||
const themeColors = useThemeColors();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isSignUp, setIsSignUp] = useState(false);
|
||||
const [isResetPassword, setIsResetPassword] = useState(false);
|
||||
|
||||
async function signInWithEmail() {
|
||||
setLoading(true);
|
||||
const { error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
async function signInWithEmail() {
|
||||
setLoading(true);
|
||||
const { error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
Alert.alert('Fehler bei der Anmeldung', error.message);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
if (error) {
|
||||
Alert.alert('Fehler bei der Anmeldung', error.message);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
async function signUpWithEmail() {
|
||||
setLoading(true);
|
||||
const { error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
async function signUpWithEmail() {
|
||||
setLoading(true);
|
||||
const { error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
Alert.alert('Fehler bei der Registrierung', error.message);
|
||||
} else {
|
||||
Alert.alert(
|
||||
'Registrierung erfolgreich',
|
||||
'Bitte überprüfen Sie Ihre E-Mail für den Bestätigungslink.'
|
||||
);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
if (error) {
|
||||
Alert.alert('Fehler bei der Registrierung', error.message);
|
||||
} else {
|
||||
Alert.alert(
|
||||
'Registrierung erfolgreich',
|
||||
'Bitte überprüfen Sie Ihre E-Mail für den Bestätigungslink.'
|
||||
);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
async function resetPassword() {
|
||||
console.log('Reset password called with email:', email);
|
||||
|
||||
if (!email) {
|
||||
Alert.alert('Fehler', 'Bitte geben Sie Ihre E-Mail-Adresse ein');
|
||||
return;
|
||||
}
|
||||
async function resetPassword() {
|
||||
console.log('Reset password called with email:', email);
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const apiUrl = process.env.EXPO_PUBLIC_API_URL || 'https://mana-core-middleware-111768794939.europe-west3.run.app';
|
||||
const endpoint = `${apiUrl}/auth/reset-password`;
|
||||
|
||||
console.log('Calling API endpoint:', endpoint);
|
||||
console.log('Request body:', { email });
|
||||
|
||||
// Call your backend endpoint for password reset
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
if (!email) {
|
||||
Alert.alert('Fehler', 'Bitte geben Sie Ihre E-Mail-Adresse ein');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Response status:', response.status);
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Response data:', data);
|
||||
setLoading(true);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Fehler beim Zurücksetzen des Passworts');
|
||||
}
|
||||
try {
|
||||
const apiUrl =
|
||||
process.env.EXPO_PUBLIC_API_URL ||
|
||||
'https://mana-core-middleware-111768794939.europe-west3.run.app';
|
||||
const endpoint = `${apiUrl}/auth/reset-password`;
|
||||
|
||||
Alert.alert(
|
||||
'E-Mail gesendet',
|
||||
'Bitte überprüfen Sie Ihre E-Mail für den Link zum Zurücksetzen des Passworts.'
|
||||
);
|
||||
setIsResetPassword(false);
|
||||
} catch (error: any) {
|
||||
console.error('Reset password error:', error);
|
||||
Alert.alert('Fehler', error.message || 'Netzwerkfehler. Bitte versuchen Sie es später erneut.');
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
console.log('Calling API endpoint:', endpoint);
|
||||
console.log('Request body:', { email });
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: isDarkMode ? '#121212' : '#F5F5F5' }]}>
|
||||
<View style={[styles.formContainer, { backgroundColor: isDarkMode ? '#1E1E1E' : '#FFFFFF', borderColor: isDarkMode ? '#374151' : '#E5E7EB' }]}>
|
||||
<View style={styles.logoContainer}>
|
||||
<FontAwesome5 name="fire" size={40} color="#F59E0B" style={styles.logoIcon} />
|
||||
<Text style={[styles.logoText, { color: isDarkMode ? '#F9FAFB' : '#1F2937' }]}>ManaCore</Text>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.header, { color: isDarkMode ? '#F9FAFB' : '#1F2937' }]}>
|
||||
{isResetPassword ? 'Passwort zurücksetzen' : isSignUp ? 'Registrieren' : 'Anmelden'}
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.subHeader, { color: isDarkMode ? '#9CA3AF' : '#6B7280' }]}>
|
||||
{isResetPassword
|
||||
? 'Geben Sie Ihre E-Mail-Adresse ein, um Ihr Passwort zurückzusetzen'
|
||||
: isSignUp
|
||||
? 'Erstellen Sie ein neues Konto, um Mana zu teilen und zu verwalten'
|
||||
: 'Melden Sie sich an, um Ihre Mana zu verwalten'}
|
||||
</Text>
|
||||
|
||||
<View style={styles.inputContainer}>
|
||||
<FontAwesome5 name="envelope" size={16} color={isDarkMode ? '#9CA3AF' : '#6B7280'} style={styles.inputIcon} />
|
||||
<TextInput
|
||||
style={[styles.input, {
|
||||
backgroundColor: isDarkMode ? '#374151' : '#F9FAFB',
|
||||
borderColor: isDarkMode ? '#4B5563' : '#E5E7EB',
|
||||
color: isDarkMode ? '#F9FAFB' : '#1F2937'
|
||||
}]}
|
||||
placeholder="E-Mail"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#6B7280'}
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
autoCapitalize="none"
|
||||
keyboardType="email-address"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!isResetPassword && (
|
||||
<View style={styles.inputContainer}>
|
||||
<FontAwesome5 name="lock" size={16} color={isDarkMode ? '#9CA3AF' : '#6B7280'} style={styles.inputIcon} />
|
||||
<TextInput
|
||||
style={[styles.input, {
|
||||
backgroundColor: isDarkMode ? '#374151' : '#F9FAFB',
|
||||
borderColor: isDarkMode ? '#4B5563' : '#E5E7EB',
|
||||
color: isDarkMode ? '#F9FAFB' : '#1F2937'
|
||||
}]}
|
||||
placeholder="Passwort"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#6B7280'}
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
// Call your backend endpoint for password reset
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.button, {
|
||||
backgroundColor: themeColors.primary,
|
||||
opacity: loading ? 0.7 : 1
|
||||
}]}
|
||||
onPress={() => {
|
||||
if (isResetPassword) {
|
||||
resetPassword();
|
||||
} else if (isSignUp) {
|
||||
signUpWithEmail();
|
||||
} else {
|
||||
signInWithEmail();
|
||||
}
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<Text style={styles.buttonText}>Lädt</Text>
|
||||
<View style={styles.dotsContainer}>
|
||||
<Text style={styles.dots}>.</Text>
|
||||
<Text style={styles.dots}>.</Text>
|
||||
<Text style={styles.dots}>.</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.buttonText}>
|
||||
{isResetPassword ? 'Link senden' : isSignUp ? 'Registrieren' : 'Anmelden'}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
console.log('Response status:', response.status);
|
||||
|
||||
{Platform.OS === 'web' && !isSignUp && !isResetPassword && (
|
||||
<TouchableOpacity
|
||||
style={styles.forgotPasswordButton}
|
||||
onPress={() => setIsResetPassword(true)}
|
||||
>
|
||||
<Text style={[styles.forgotPasswordText, { color: themeColors.primary }]}>
|
||||
Passwort vergessen?
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
const data = await response.json();
|
||||
console.log('Response data:', data);
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.switchButton}
|
||||
onPress={() => {
|
||||
if (isResetPassword) {
|
||||
setIsResetPassword(false);
|
||||
} else {
|
||||
setIsSignUp(!isSignUp);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.switchText, { color: themeColors.primary }]}>
|
||||
{isResetPassword
|
||||
? 'Zurück zur Anmeldung'
|
||||
: isSignUp
|
||||
? 'Bereits ein Konto? Anmelden'
|
||||
: 'Kein Konto? Registrieren'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.footer}>
|
||||
<Text style={[styles.footerText, { color: isDarkMode ? '#9CA3AF' : '#6B7280' }]}>
|
||||
© {new Date().getFullYear()} ManaCore. Alle Rechte vorbehalten.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Fehler beim Zurücksetzen des Passworts');
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'E-Mail gesendet',
|
||||
'Bitte überprüfen Sie Ihre E-Mail für den Link zum Zurücksetzen des Passworts.'
|
||||
);
|
||||
setIsResetPassword(false);
|
||||
} catch (error: any) {
|
||||
console.error('Reset password error:', error);
|
||||
Alert.alert(
|
||||
'Fehler',
|
||||
error.message || 'Netzwerkfehler. Bitte versuchen Sie es später erneut.'
|
||||
);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: isDarkMode ? '#121212' : '#F5F5F5' }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.formContainer,
|
||||
{
|
||||
backgroundColor: isDarkMode ? '#1E1E1E' : '#FFFFFF',
|
||||
borderColor: isDarkMode ? '#374151' : '#E5E7EB',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={styles.logoContainer}>
|
||||
<FontAwesome5 name="fire" size={40} color="#F59E0B" style={styles.logoIcon} />
|
||||
<Text style={[styles.logoText, { color: isDarkMode ? '#F9FAFB' : '#1F2937' }]}>
|
||||
ManaCore
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.header, { color: isDarkMode ? '#F9FAFB' : '#1F2937' }]}>
|
||||
{isResetPassword ? 'Passwort zurücksetzen' : isSignUp ? 'Registrieren' : 'Anmelden'}
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.subHeader, { color: isDarkMode ? '#9CA3AF' : '#6B7280' }]}>
|
||||
{isResetPassword
|
||||
? 'Geben Sie Ihre E-Mail-Adresse ein, um Ihr Passwort zurückzusetzen'
|
||||
: isSignUp
|
||||
? 'Erstellen Sie ein neues Konto, um Mana zu teilen und zu verwalten'
|
||||
: 'Melden Sie sich an, um Ihre Mana zu verwalten'}
|
||||
</Text>
|
||||
|
||||
<View style={styles.inputContainer}>
|
||||
<FontAwesome5
|
||||
name="envelope"
|
||||
size={16}
|
||||
color={isDarkMode ? '#9CA3AF' : '#6B7280'}
|
||||
style={styles.inputIcon}
|
||||
/>
|
||||
<TextInput
|
||||
style={[
|
||||
styles.input,
|
||||
{
|
||||
backgroundColor: isDarkMode ? '#374151' : '#F9FAFB',
|
||||
borderColor: isDarkMode ? '#4B5563' : '#E5E7EB',
|
||||
color: isDarkMode ? '#F9FAFB' : '#1F2937',
|
||||
},
|
||||
]}
|
||||
placeholder="E-Mail"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#6B7280'}
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
autoCapitalize="none"
|
||||
keyboardType="email-address"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!isResetPassword && (
|
||||
<View style={styles.inputContainer}>
|
||||
<FontAwesome5
|
||||
name="lock"
|
||||
size={16}
|
||||
color={isDarkMode ? '#9CA3AF' : '#6B7280'}
|
||||
style={styles.inputIcon}
|
||||
/>
|
||||
<TextInput
|
||||
style={[
|
||||
styles.input,
|
||||
{
|
||||
backgroundColor: isDarkMode ? '#374151' : '#F9FAFB',
|
||||
borderColor: isDarkMode ? '#4B5563' : '#E5E7EB',
|
||||
color: isDarkMode ? '#F9FAFB' : '#1F2937',
|
||||
},
|
||||
]}
|
||||
placeholder="Passwort"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#6B7280'}
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.button,
|
||||
{
|
||||
backgroundColor: themeColors.primary,
|
||||
opacity: loading ? 0.7 : 1,
|
||||
},
|
||||
]}
|
||||
onPress={() => {
|
||||
if (isResetPassword) {
|
||||
resetPassword();
|
||||
} else if (isSignUp) {
|
||||
signUpWithEmail();
|
||||
} else {
|
||||
signInWithEmail();
|
||||
}
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<Text style={styles.buttonText}>Lädt</Text>
|
||||
<View style={styles.dotsContainer}>
|
||||
<Text style={styles.dots}>.</Text>
|
||||
<Text style={styles.dots}>.</Text>
|
||||
<Text style={styles.dots}>.</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.buttonText}>
|
||||
{isResetPassword ? 'Link senden' : isSignUp ? 'Registrieren' : 'Anmelden'}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
{Platform.OS === 'web' && !isSignUp && !isResetPassword && (
|
||||
<TouchableOpacity
|
||||
style={styles.forgotPasswordButton}
|
||||
onPress={() => setIsResetPassword(true)}
|
||||
>
|
||||
<Text style={[styles.forgotPasswordText, { color: themeColors.primary }]}>
|
||||
Passwort vergessen?
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.switchButton}
|
||||
onPress={() => {
|
||||
if (isResetPassword) {
|
||||
setIsResetPassword(false);
|
||||
} else {
|
||||
setIsSignUp(!isSignUp);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.switchText, { color: themeColors.primary }]}>
|
||||
{isResetPassword
|
||||
? 'Zurück zur Anmeldung'
|
||||
: isSignUp
|
||||
? 'Bereits ein Konto? Anmelden'
|
||||
: 'Kein Konto? Registrieren'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.footer}>
|
||||
<Text style={[styles.footerText, { color: isDarkMode ? '#9CA3AF' : '#6B7280' }]}>
|
||||
© {new Date().getFullYear()} ManaCore. Alle Rechte vorbehalten.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
formContainer: {
|
||||
padding: 24,
|
||||
borderRadius: 12,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
borderWidth: 1,
|
||||
maxWidth: 500,
|
||||
width: '100%',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
logoContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 24,
|
||||
},
|
||||
logoIcon: {
|
||||
marginRight: 12,
|
||||
},
|
||||
logoText: {
|
||||
fontSize: 28,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
header: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
subHeader: {
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
marginBottom: 24,
|
||||
},
|
||||
inputContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
inputIcon: {
|
||||
marginRight: 12,
|
||||
},
|
||||
input: {
|
||||
flex: 1,
|
||||
height: 50,
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 16,
|
||||
fontSize: 16,
|
||||
},
|
||||
button: {
|
||||
height: 54,
|
||||
borderRadius: 8,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 24,
|
||||
},
|
||||
buttonText: {
|
||||
color: 'white',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
loadingContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
dotsContainer: {
|
||||
flexDirection: 'row',
|
||||
marginLeft: 4,
|
||||
},
|
||||
dots: {
|
||||
color: 'white',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
marginLeft: 2,
|
||||
},
|
||||
switchButton: {
|
||||
marginTop: 24,
|
||||
alignItems: 'center',
|
||||
},
|
||||
switchText: {
|
||||
fontSize: 16,
|
||||
},
|
||||
forgotPasswordButton: {
|
||||
marginTop: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
forgotPasswordText: {
|
||||
fontSize: 14,
|
||||
},
|
||||
footer: {
|
||||
marginTop: 40,
|
||||
alignItems: 'center',
|
||||
},
|
||||
footerText: {
|
||||
fontSize: 12,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
formContainer: {
|
||||
padding: 24,
|
||||
borderRadius: 12,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
borderWidth: 1,
|
||||
maxWidth: 500,
|
||||
width: '100%',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
logoContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 24,
|
||||
},
|
||||
logoIcon: {
|
||||
marginRight: 12,
|
||||
},
|
||||
logoText: {
|
||||
fontSize: 28,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
header: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
subHeader: {
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
marginBottom: 24,
|
||||
},
|
||||
inputContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
inputIcon: {
|
||||
marginRight: 12,
|
||||
},
|
||||
input: {
|
||||
flex: 1,
|
||||
height: 50,
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 16,
|
||||
fontSize: 16,
|
||||
},
|
||||
button: {
|
||||
height: 54,
|
||||
borderRadius: 8,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 24,
|
||||
},
|
||||
buttonText: {
|
||||
color: 'white',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
loadingContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
dotsContainer: {
|
||||
flexDirection: 'row',
|
||||
marginLeft: 4,
|
||||
},
|
||||
dots: {
|
||||
color: 'white',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
marginLeft: 2,
|
||||
},
|
||||
switchButton: {
|
||||
marginTop: 24,
|
||||
alignItems: 'center',
|
||||
},
|
||||
switchText: {
|
||||
fontSize: 16,
|
||||
},
|
||||
forgotPasswordButton: {
|
||||
marginTop: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
forgotPasswordText: {
|
||||
fontSize: 14,
|
||||
},
|
||||
footer: {
|
||||
marginTop: 40,
|
||||
alignItems: 'center',
|
||||
},
|
||||
footerText: {
|
||||
fontSize: 12,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,21 +2,22 @@ import { forwardRef } from 'react';
|
|||
import { Text, TouchableOpacity, TouchableOpacityProps, View } from 'react-native';
|
||||
|
||||
type ButtonProps = {
|
||||
title: string;
|
||||
title: string;
|
||||
} & TouchableOpacityProps;
|
||||
|
||||
export const Button = forwardRef<View, ButtonProps>(({ title, ...touchableProps }, ref) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
ref={ref}
|
||||
{...touchableProps}
|
||||
className={`${styles.button} ${touchableProps.className}`}>
|
||||
<Text className={styles.buttonText}>{title}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
ref={ref}
|
||||
{...touchableProps}
|
||||
className={`${styles.button} ${touchableProps.className}`}
|
||||
>
|
||||
<Text className={styles.buttonText}>{title}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
});
|
||||
|
||||
const styles = {
|
||||
button: 'items-center bg-indigo-500 rounded-[28px] shadow-md p-4',
|
||||
buttonText: 'text-white text-lg font-semibold text-center',
|
||||
button: 'items-center bg-indigo-500 rounded-[28px] shadow-md p-4',
|
||||
buttonText: 'text-white text-lg font-semibold text-center',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import { SafeAreaView, View } from 'react-native';
|
|||
import { useTheme } from '~/utils/themeContext';
|
||||
|
||||
export const Container = ({ children }: { children: React.ReactNode }) => {
|
||||
const { isDarkMode } = useTheme();
|
||||
|
||||
return (
|
||||
<SafeAreaView className={`flex flex-1 ${isDarkMode ? 'bg-gray-900' : 'bg-white'}`}>
|
||||
<View className="flex flex-1 m-6">{children}</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
const { isDarkMode } = useTheme();
|
||||
|
||||
return (
|
||||
<SafeAreaView className={`flex flex-1 ${isDarkMode ? 'bg-gray-900' : 'bg-white'}`}>
|
||||
<View className="m-6 flex flex-1">{children}</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,243 +1,272 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, TextInput, TouchableOpacity, Alert, ActivityIndicator, ScrollView } from 'react-native';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { useTheme } from '../utils/themeContext';
|
||||
|
||||
interface UserRole {
|
||||
role_id: string;
|
||||
roles?: {
|
||||
name: string;
|
||||
};
|
||||
role_id: string;
|
||||
roles?: {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateOrganizationProps {
|
||||
onOrgCreated?: (orgId: string, orgName: string) => void;
|
||||
onOrgCreated?: (orgId: string, orgName: string) => void;
|
||||
}
|
||||
|
||||
export default function CreateOrganization({ onOrgCreated }: CreateOrganizationProps) {
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [organizationName, setOrganizationName] = useState('');
|
||||
const [initialCredits, setInitialCredits] = useState('');
|
||||
const [userHasPermission, setUserHasPermission] = useState(false);
|
||||
const [checkingPermission, setCheckingPermission] = useState(true);
|
||||
const { isDarkMode } = useTheme();
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [organizationName, setOrganizationName] = useState('');
|
||||
const [initialCredits, setInitialCredits] = useState('');
|
||||
const [userHasPermission, setUserHasPermission] = useState(false);
|
||||
const [checkingPermission, setCheckingPermission] = useState(true);
|
||||
const { isDarkMode } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
// Prüfe den aktuellen Authentifizierungsstatus
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
checkUserPermission(session.user.id);
|
||||
} else {
|
||||
setCheckingPermission(false);
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
// Prüfe den aktuellen Authentifizierungsstatus
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
checkUserPermission(session.user.id);
|
||||
} else {
|
||||
setCheckingPermission(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Abonniere Authentifizierungsänderungen
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
checkUserPermission(session.user.id);
|
||||
} else {
|
||||
setCheckingPermission(false);
|
||||
}
|
||||
});
|
||||
// Abonniere Authentifizierungsänderungen
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
checkUserPermission(session.user.id);
|
||||
} else {
|
||||
setCheckingPermission(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
async function checkUserPermission(userId: string) {
|
||||
try {
|
||||
setCheckingPermission(true);
|
||||
|
||||
// Prüfe, ob der Benutzer ein System-Administrator ist
|
||||
const { data: userRoles, error: userRolesError } = await supabase
|
||||
.from('user_roles')
|
||||
.select('role_id, roles(name)')
|
||||
.eq('user_id', userId) as { data: UserRole[] | null, error: any };
|
||||
async function checkUserPermission(userId: string) {
|
||||
try {
|
||||
setCheckingPermission(true);
|
||||
|
||||
if (userRolesError) throw userRolesError;
|
||||
// Prüfe, ob der Benutzer ein System-Administrator ist
|
||||
const { data: userRoles, error: userRolesError } = (await supabase
|
||||
.from('user_roles')
|
||||
.select('role_id, roles(name)')
|
||||
.eq('user_id', userId)) as { data: UserRole[] | null; error: any };
|
||||
|
||||
if (userRoles && userRoles.length > 0) {
|
||||
// Prüfe, ob der Benutzer die Rolle "system_admin" hat
|
||||
const isSystemAdmin = userRoles.some(role => {
|
||||
const roleName = role.roles?.name;
|
||||
return roleName === 'system_admin';
|
||||
});
|
||||
if (userRolesError) throw userRolesError;
|
||||
|
||||
setUserHasPermission(isSystemAdmin);
|
||||
} else {
|
||||
setUserHasPermission(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Prüfen der Benutzerberechtigungen:', error);
|
||||
setUserHasPermission(false);
|
||||
} finally {
|
||||
setCheckingPermission(false);
|
||||
}
|
||||
}
|
||||
if (userRoles && userRoles.length > 0) {
|
||||
// Prüfe, ob der Benutzer die Rolle "system_admin" hat
|
||||
const isSystemAdmin = userRoles.some((role) => {
|
||||
const roleName = role.roles?.name;
|
||||
return roleName === 'system_admin';
|
||||
});
|
||||
|
||||
async function createOrganization() {
|
||||
if (!session) {
|
||||
Alert.alert('Fehler', 'Sie müssen angemeldet sein, um eine Organisation zu erstellen.');
|
||||
return;
|
||||
}
|
||||
setUserHasPermission(isSystemAdmin);
|
||||
} else {
|
||||
setUserHasPermission(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Prüfen der Benutzerberechtigungen:', error);
|
||||
setUserHasPermission(false);
|
||||
} finally {
|
||||
setCheckingPermission(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!userHasPermission) {
|
||||
Alert.alert('Fehler', 'Sie haben keine Berechtigung, Organisationen zu erstellen.');
|
||||
return;
|
||||
}
|
||||
async function createOrganization() {
|
||||
if (!session) {
|
||||
Alert.alert('Fehler', 'Sie müssen angemeldet sein, um eine Organisation zu erstellen.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!organizationName.trim()) {
|
||||
Alert.alert('Fehler', 'Bitte geben Sie einen Organisationsnamen ein.');
|
||||
return;
|
||||
}
|
||||
if (!userHasPermission) {
|
||||
Alert.alert('Fehler', 'Sie haben keine Berechtigung, Organisationen zu erstellen.');
|
||||
return;
|
||||
}
|
||||
|
||||
const credits = parseInt(initialCredits);
|
||||
if (isNaN(credits) || credits < 0) {
|
||||
Alert.alert('Fehler', 'Bitte geben Sie eine gültige Anzahl an Krediten ein.');
|
||||
return;
|
||||
}
|
||||
if (!organizationName.trim()) {
|
||||
Alert.alert('Fehler', 'Bitte geben Sie einen Organisationsnamen ein.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const credits = parseInt(initialCredits);
|
||||
if (isNaN(credits) || credits < 0) {
|
||||
Alert.alert('Fehler', 'Bitte geben Sie eine gültige Anzahl an Krediten ein.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Erstelle die Organisation
|
||||
const { data: organization, error: orgError } = await supabase
|
||||
.from('organizations')
|
||||
.insert([
|
||||
{
|
||||
name: organizationName.trim(),
|
||||
total_credits: credits,
|
||||
used_credits: 0
|
||||
}
|
||||
])
|
||||
.select()
|
||||
.single();
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
if (orgError) throw orgError;
|
||||
// 1. Erstelle die Organisation
|
||||
const { data: organization, error: orgError } = await supabase
|
||||
.from('organizations')
|
||||
.insert([
|
||||
{
|
||||
name: organizationName.trim(),
|
||||
total_credits: credits,
|
||||
used_credits: 0,
|
||||
},
|
||||
])
|
||||
.select()
|
||||
.single();
|
||||
|
||||
// 2. Hole die org_admin Rolle
|
||||
const { data: adminRole, error: roleError } = await supabase
|
||||
.from('roles')
|
||||
.select('id')
|
||||
.eq('name', 'org_admin')
|
||||
.single();
|
||||
if (orgError) throw orgError;
|
||||
|
||||
if (roleError) throw roleError;
|
||||
// 2. Hole die org_admin Rolle
|
||||
const { data: adminRole, error: roleError } = await supabase
|
||||
.from('roles')
|
||||
.select('id')
|
||||
.eq('name', 'org_admin')
|
||||
.single();
|
||||
|
||||
// 3. Füge den aktuellen Benutzer als Organisations-Administrator hinzu
|
||||
const { error: userRoleError } = await supabase
|
||||
.from('user_roles')
|
||||
.insert([
|
||||
{
|
||||
user_id: session.user.id,
|
||||
role_id: adminRole.id,
|
||||
organization_id: organization.id
|
||||
}
|
||||
]);
|
||||
if (roleError) throw roleError;
|
||||
|
||||
if (userRoleError) throw userRoleError;
|
||||
// 3. Füge den aktuellen Benutzer als Organisations-Administrator hinzu
|
||||
const { error: userRoleError } = await supabase.from('user_roles').insert([
|
||||
{
|
||||
user_id: session.user.id,
|
||||
role_id: adminRole.id,
|
||||
organization_id: organization.id,
|
||||
},
|
||||
]);
|
||||
|
||||
// Erfolgsbenachrichtigung anzeigen und direkt navigieren
|
||||
Alert.alert(
|
||||
'Erfolg',
|
||||
`Die Organisation "${organizationName}" wurde erfolgreich erstellt.`
|
||||
);
|
||||
|
||||
// Direkt zur Organisationsdetailseite navigieren, ohne auf OK zu warten
|
||||
if (onOrgCreated) {
|
||||
console.log('Navigiere zur neuen Organisationsseite:', organization.id, organization.name);
|
||||
onOrgCreated(organization.id, organization.name);
|
||||
}
|
||||
|
||||
// Formular zurücksetzen
|
||||
setOrganizationName('');
|
||||
setInitialCredits('');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen der Organisation:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Erstellen der Organisation aufgetreten.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
if (userRoleError) throw userRoleError;
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-5 m-2.5 shadow`}>
|
||||
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center p-5`}>
|
||||
Bitte melden Sie sich an, um Organisationen zu erstellen.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Erfolgsbenachrichtigung anzeigen und direkt navigieren
|
||||
Alert.alert('Erfolg', `Die Organisation "${organizationName}" wurde erfolgreich erstellt.`);
|
||||
|
||||
if (checkingPermission) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-5 m-2.5 shadow`}>
|
||||
<ActivityIndicator size="large" color={isDarkMode ? '#93C5FD' : '#0055FF'} />
|
||||
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center mt-2.5`}>Prüfe Berechtigungen...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Direkt zur Organisationsdetailseite navigieren, ohne auf OK zu warten
|
||||
if (onOrgCreated) {
|
||||
console.log('Navigiere zur neuen Organisationsseite:', organization.id, organization.name);
|
||||
onOrgCreated(organization.id, organization.name);
|
||||
}
|
||||
|
||||
if (!userHasPermission) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-5 m-2.5 shadow`}>
|
||||
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center p-5`}>
|
||||
Sie haben keine Berechtigung, Organisationen zu erstellen. Bitte kontaktieren Sie einen Administrator.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Formular zurücksetzen
|
||||
setOrganizationName('');
|
||||
setInitialCredits('');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen der Organisation:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Erstellen der Organisation aufgetreten.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView className="flex-1">
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-5 m-2.5 shadow`}>
|
||||
<Text className={`text-2xl font-bold mb-5 ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>Neue Organisation erstellen</Text>
|
||||
|
||||
<View className="mb-4">
|
||||
<Text className={`text-base font-medium mb-2 ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>Organisationsname</Text>
|
||||
<TextInput
|
||||
className={`h-12 border ${isDarkMode ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-300 bg-gray-50 text-gray-800'} rounded-lg px-4 text-base`}
|
||||
value={organizationName}
|
||||
onChangeText={setOrganizationName}
|
||||
placeholder="Name der Organisation eingeben"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#9CA3AF'}
|
||||
/>
|
||||
</View>
|
||||
if (!session) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-5 shadow`}>
|
||||
<Text
|
||||
className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} p-5 text-center`}
|
||||
>
|
||||
Bitte melden Sie sich an, um Organisationen zu erstellen.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
<View className="mb-4">
|
||||
<Text className={`text-base font-medium mb-2 ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>Anfängliche Kredite</Text>
|
||||
<TextInput
|
||||
className={`h-12 border ${isDarkMode ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-300 bg-gray-50 text-gray-800'} rounded-lg px-4 text-base`}
|
||||
value={initialCredits}
|
||||
onChangeText={setInitialCredits}
|
||||
placeholder="Anzahl der Kredite eingeben"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#9CA3AF'}
|
||||
keyboardType="number-pad"
|
||||
/>
|
||||
<Text className={`text-sm mt-1 ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
|
||||
Dies ist die Gesamtanzahl der Kredite, die dieser Organisation zur Verfügung stehen werden.
|
||||
</Text>
|
||||
</View>
|
||||
if (checkingPermission) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-5 shadow`}>
|
||||
<ActivityIndicator size="large" color={isDarkMode ? '#93C5FD' : '#0055FF'} />
|
||||
<Text
|
||||
className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} mt-2.5 text-center`}
|
||||
>
|
||||
Prüfe Berechtigungen...
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
<TouchableOpacity
|
||||
className={`${isDarkMode ? 'bg-blue-700' : 'bg-blue-600'} py-3 px-4 rounded-lg ${loading ? 'opacity-70' : ''}`}
|
||||
onPress={createOrganization}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : (
|
||||
<Text className="text-white font-semibold text-center text-base">Organisation erstellen</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
if (!userHasPermission) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-5 shadow`}>
|
||||
<Text
|
||||
className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} p-5 text-center`}
|
||||
>
|
||||
Sie haben keine Berechtigung, Organisationen zu erstellen. Bitte kontaktieren Sie einen
|
||||
Administrator.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView className="flex-1">
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-5 shadow`}>
|
||||
<Text
|
||||
className={`mb-5 text-2xl font-bold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
Neue Organisation erstellen
|
||||
</Text>
|
||||
|
||||
<View className="mb-4">
|
||||
<Text
|
||||
className={`mb-2 text-base font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
Organisationsname
|
||||
</Text>
|
||||
<TextInput
|
||||
className={`h-12 border ${isDarkMode ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-300 bg-gray-50 text-gray-800'} rounded-lg px-4 text-base`}
|
||||
value={organizationName}
|
||||
onChangeText={setOrganizationName}
|
||||
placeholder="Name der Organisation eingeben"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#9CA3AF'}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View className="mb-4">
|
||||
<Text
|
||||
className={`mb-2 text-base font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
Anfängliche Kredite
|
||||
</Text>
|
||||
<TextInput
|
||||
className={`h-12 border ${isDarkMode ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-300 bg-gray-50 text-gray-800'} rounded-lg px-4 text-base`}
|
||||
value={initialCredits}
|
||||
onChangeText={setInitialCredits}
|
||||
placeholder="Anzahl der Kredite eingeben"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#9CA3AF'}
|
||||
keyboardType="number-pad"
|
||||
/>
|
||||
<Text className={`mt-1 text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
|
||||
Dies ist die Gesamtanzahl der Kredite, die dieser Organisation zur Verfügung stehen
|
||||
werden.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
className={`${isDarkMode ? 'bg-blue-700' : 'bg-blue-600'} rounded-lg px-4 py-3 ${loading ? 'opacity-70' : ''}`}
|
||||
onPress={createOrganization}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : (
|
||||
<Text className="text-center text-base font-semibold text-white">
|
||||
Organisation erstellen
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
// NativeWind wird für das Styling verwendet, daher sind keine StyleSheet-Definitionen erforderlich
|
||||
|
|
|
|||
|
|
@ -1,326 +1,356 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, TextInput, TouchableOpacity, Alert, ActivityIndicator, ScrollView } from 'react-native';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import { useTheme } from '../utils/themeContext';
|
||||
import { useRouter } from 'expo-router';
|
||||
|
||||
interface Organization {
|
||||
id: string;
|
||||
name: string;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UserRole {
|
||||
organization_id: string;
|
||||
roles?: {
|
||||
name: string;
|
||||
};
|
||||
organization_id: string;
|
||||
roles?: {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateTeamProps {
|
||||
onTeamCreated?: (teamId: string, teamName: string) => void;
|
||||
onTeamCreated?: (teamId: string, teamName: string) => void;
|
||||
}
|
||||
|
||||
export default function CreateTeam({ onTeamCreated }: CreateTeamProps) {
|
||||
const router = useRouter();
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||
const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);
|
||||
const [teamName, setTeamName] = useState('');
|
||||
const [allocatedCredits, setAllocatedCredits] = useState('');
|
||||
const [fetchingOrgs, setFetchingOrgs] = useState(true);
|
||||
const { isDarkMode } = useTheme();
|
||||
const router = useRouter();
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||
const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);
|
||||
const [teamName, setTeamName] = useState('');
|
||||
const [allocatedCredits, setAllocatedCredits] = useState('');
|
||||
const [fetchingOrgs, setFetchingOrgs] = useState(true);
|
||||
const { isDarkMode } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
// Prüfe den aktuellen Authentifizierungsstatus
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserOrganizations(session.user.id);
|
||||
} else {
|
||||
setFetchingOrgs(false);
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
// Prüfe den aktuellen Authentifizierungsstatus
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserOrganizations(session.user.id);
|
||||
} else {
|
||||
setFetchingOrgs(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Abonniere Authentifizierungsänderungen
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserOrganizations(session.user.id);
|
||||
} else {
|
||||
setFetchingOrgs(false);
|
||||
}
|
||||
});
|
||||
// Abonniere Authentifizierungsänderungen
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserOrganizations(session.user.id);
|
||||
} else {
|
||||
setFetchingOrgs(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
async function fetchUserOrganizations(userId: string) {
|
||||
try {
|
||||
setFetchingOrgs(true);
|
||||
|
||||
// Suche nach Organisationen, in denen der Benutzer eine Rolle hat
|
||||
const { data: userRoles, error: userRolesError } = await supabase
|
||||
.from('user_roles')
|
||||
.select('organization_id, roles(name)')
|
||||
.eq('user_id', userId)
|
||||
.not('organization_id', 'is', null) as { data: UserRole[] | null, error: any };
|
||||
async function fetchUserOrganizations(userId: string) {
|
||||
try {
|
||||
setFetchingOrgs(true);
|
||||
|
||||
if (userRolesError) throw userRolesError;
|
||||
// Suche nach Organisationen, in denen der Benutzer eine Rolle hat
|
||||
const { data: userRoles, error: userRolesError } = (await supabase
|
||||
.from('user_roles')
|
||||
.select('organization_id, roles(name)')
|
||||
.eq('user_id', userId)
|
||||
.not('organization_id', 'is', null)) as { data: UserRole[] | null; error: any };
|
||||
|
||||
if (userRoles && userRoles.length > 0) {
|
||||
// Extrahiere die Organisations-IDs
|
||||
const orgIds = userRoles
|
||||
.filter(role => {
|
||||
// Prüfe, ob der Benutzer ein Administrator oder Manager ist
|
||||
const roleName = role.roles?.name;
|
||||
return roleName === 'system_admin' ||
|
||||
roleName === 'org_admin' ||
|
||||
roleName === 'team_admin';
|
||||
})
|
||||
.map(role => role.organization_id);
|
||||
if (userRolesError) throw userRolesError;
|
||||
|
||||
if (orgIds.length > 0) {
|
||||
// Hole die Details der Organisationen
|
||||
const { data: orgs, error: orgsError } = await supabase
|
||||
.from('organizations')
|
||||
.select('id, name')
|
||||
.in('id', orgIds);
|
||||
if (userRoles && userRoles.length > 0) {
|
||||
// Extrahiere die Organisations-IDs
|
||||
const orgIds = userRoles
|
||||
.filter((role) => {
|
||||
// Prüfe, ob der Benutzer ein Administrator oder Manager ist
|
||||
const roleName = role.roles?.name;
|
||||
return (
|
||||
roleName === 'system_admin' || roleName === 'org_admin' || roleName === 'team_admin'
|
||||
);
|
||||
})
|
||||
.map((role) => role.organization_id);
|
||||
|
||||
if (orgsError) throw orgsError;
|
||||
if (orgIds.length > 0) {
|
||||
// Hole die Details der Organisationen
|
||||
const { data: orgs, error: orgsError } = await supabase
|
||||
.from('organizations')
|
||||
.select('id, name')
|
||||
.in('id', orgIds);
|
||||
|
||||
if (orgs && orgs.length > 0) {
|
||||
setOrganizations(orgs);
|
||||
// Setze die erste Organisation als Standard
|
||||
setSelectedOrgId(orgs[0].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Organisationen:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Laden der Organisationen aufgetreten.');
|
||||
} finally {
|
||||
setFetchingOrgs(false);
|
||||
}
|
||||
}
|
||||
if (orgsError) throw orgsError;
|
||||
|
||||
async function createTeam() {
|
||||
if (!session) {
|
||||
Alert.alert('Fehler', 'Sie müssen angemeldet sein, um ein Team zu erstellen.');
|
||||
return;
|
||||
}
|
||||
if (orgs && orgs.length > 0) {
|
||||
setOrganizations(orgs);
|
||||
// Setze die erste Organisation als Standard
|
||||
setSelectedOrgId(orgs[0].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Organisationen:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Laden der Organisationen aufgetreten.');
|
||||
} finally {
|
||||
setFetchingOrgs(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectedOrgId) {
|
||||
Alert.alert('Fehler', 'Bitte wählen Sie eine Organisation aus.');
|
||||
return;
|
||||
}
|
||||
async function createTeam() {
|
||||
if (!session) {
|
||||
Alert.alert('Fehler', 'Sie müssen angemeldet sein, um ein Team zu erstellen.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!teamName.trim()) {
|
||||
Alert.alert('Fehler', 'Bitte geben Sie einen Teamnamen ein.');
|
||||
return;
|
||||
}
|
||||
if (!selectedOrgId) {
|
||||
Alert.alert('Fehler', 'Bitte wählen Sie eine Organisation aus.');
|
||||
return;
|
||||
}
|
||||
|
||||
const credits = parseInt(allocatedCredits);
|
||||
if (isNaN(credits) || credits < 0) {
|
||||
Alert.alert('Fehler', 'Bitte geben Sie eine gültige Anzahl an Krediten ein.');
|
||||
return;
|
||||
}
|
||||
if (!teamName.trim()) {
|
||||
Alert.alert('Fehler', 'Bitte geben Sie einen Teamnamen ein.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const credits = parseInt(allocatedCredits);
|
||||
if (isNaN(credits) || credits < 0) {
|
||||
Alert.alert('Fehler', 'Bitte geben Sie eine gültige Anzahl an Krediten ein.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Prüfe, ob die Organisation genügend Kredite hat
|
||||
const { data: org, error: orgError } = await supabase
|
||||
.from('organizations')
|
||||
.select('total_credits, used_credits')
|
||||
.eq('id', selectedOrgId)
|
||||
.single();
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
if (orgError) throw orgError;
|
||||
// 1. Prüfe, ob die Organisation genügend Kredite hat
|
||||
const { data: org, error: orgError } = await supabase
|
||||
.from('organizations')
|
||||
.select('total_credits, used_credits')
|
||||
.eq('id', selectedOrgId)
|
||||
.single();
|
||||
|
||||
const availableCredits = org.total_credits - org.used_credits;
|
||||
if (credits > availableCredits) {
|
||||
Alert.alert('Fehler', `Die Organisation hat nicht genügend Kredite. Verfügbar: ${availableCredits}`);
|
||||
return;
|
||||
}
|
||||
if (orgError) throw orgError;
|
||||
|
||||
// 2. Erstelle das Team
|
||||
const { data: team, error: teamError } = await supabase
|
||||
.from('teams')
|
||||
.insert([
|
||||
{
|
||||
organization_id: selectedOrgId,
|
||||
name: teamName.trim(),
|
||||
allocated_credits: credits
|
||||
}
|
||||
])
|
||||
.select()
|
||||
.single();
|
||||
const availableCredits = org.total_credits - org.used_credits;
|
||||
if (credits > availableCredits) {
|
||||
Alert.alert(
|
||||
'Fehler',
|
||||
`Die Organisation hat nicht genügend Kredite. Verfügbar: ${availableCredits}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (teamError) throw teamError;
|
||||
// 2. Erstelle das Team
|
||||
const { data: team, error: teamError } = await supabase
|
||||
.from('teams')
|
||||
.insert([
|
||||
{
|
||||
organization_id: selectedOrgId,
|
||||
name: teamName.trim(),
|
||||
allocated_credits: credits,
|
||||
},
|
||||
])
|
||||
.select()
|
||||
.single();
|
||||
|
||||
// 3. Aktualisiere die verwendeten Kredite der Organisation
|
||||
const { error: updateOrgError } = await supabase
|
||||
.from('organizations')
|
||||
.update({
|
||||
used_credits: org.used_credits + credits
|
||||
})
|
||||
.eq('id', selectedOrgId);
|
||||
if (teamError) throw teamError;
|
||||
|
||||
if (updateOrgError) throw updateOrgError;
|
||||
// 3. Aktualisiere die verwendeten Kredite der Organisation
|
||||
const { error: updateOrgError } = await supabase
|
||||
.from('organizations')
|
||||
.update({
|
||||
used_credits: org.used_credits + credits,
|
||||
})
|
||||
.eq('id', selectedOrgId);
|
||||
|
||||
// 4. Füge den aktuellen Benutzer als Team-Administrator hinzu
|
||||
// Zuerst hole die team_admin Rolle
|
||||
const { data: adminRole, error: roleError } = await supabase
|
||||
.from('roles')
|
||||
.select('id')
|
||||
.eq('name', 'team_admin')
|
||||
.single();
|
||||
if (updateOrgError) throw updateOrgError;
|
||||
|
||||
if (roleError) throw roleError;
|
||||
// 4. Füge den aktuellen Benutzer als Team-Administrator hinzu
|
||||
// Zuerst hole die team_admin Rolle
|
||||
const { data: adminRole, error: roleError } = await supabase
|
||||
.from('roles')
|
||||
.select('id')
|
||||
.eq('name', 'team_admin')
|
||||
.single();
|
||||
|
||||
// Füge die Benutzerrolle hinzu
|
||||
const { error: userRoleError } = await supabase
|
||||
.from('user_roles')
|
||||
.insert([
|
||||
{
|
||||
user_id: session.user.id,
|
||||
role_id: adminRole.id,
|
||||
team_id: team.id,
|
||||
organization_id: selectedOrgId
|
||||
}
|
||||
]);
|
||||
if (roleError) throw roleError;
|
||||
|
||||
if (userRoleError) throw userRoleError;
|
||||
// Füge die Benutzerrolle hinzu
|
||||
const { error: userRoleError } = await supabase.from('user_roles').insert([
|
||||
{
|
||||
user_id: session.user.id,
|
||||
role_id: adminRole.id,
|
||||
team_id: team.id,
|
||||
organization_id: selectedOrgId,
|
||||
},
|
||||
]);
|
||||
|
||||
// 5. Füge den Benutzer als Teammitglied hinzu
|
||||
const { error: teamMemberError } = await supabase
|
||||
.from('team_members')
|
||||
.insert([
|
||||
{
|
||||
team_id: team.id,
|
||||
user_id: session.user.id,
|
||||
allocated_credits: 0, // Standardmäßig keine Kredite zugewiesen
|
||||
used_credits: 0
|
||||
}
|
||||
]);
|
||||
if (userRoleError) throw userRoleError;
|
||||
|
||||
if (teamMemberError) throw teamMemberError;
|
||||
// 5. Füge den Benutzer als Teammitglied hinzu
|
||||
const { error: teamMemberError } = await supabase.from('team_members').insert([
|
||||
{
|
||||
team_id: team.id,
|
||||
user_id: session.user.id,
|
||||
allocated_credits: 0, // Standardmäßig keine Kredite zugewiesen
|
||||
used_credits: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
// Erfolgsbenachrichtigung anzeigen und direkt navigieren
|
||||
Alert.alert(
|
||||
'Erfolg',
|
||||
`Das Team "${teamName}" wurde erfolgreich erstellt.`
|
||||
);
|
||||
|
||||
// Direkt zur Teamdetailseite navigieren, ohne auf OK zu warten
|
||||
if (onTeamCreated) {
|
||||
console.log('Navigiere zur neuen Teamseite:', team.id, team.name);
|
||||
onTeamCreated(team.id, team.name);
|
||||
}
|
||||
|
||||
// Formular zurücksetzen
|
||||
setTeamName('');
|
||||
setAllocatedCredits('');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Teams:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Erstellen des Teams aufgetreten.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
if (teamMemberError) throw teamMemberError;
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-5 m-2.5 shadow`}>
|
||||
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center p-5`}>
|
||||
Bitte melden Sie sich an, um Teams zu erstellen.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Erfolgsbenachrichtigung anzeigen und direkt navigieren
|
||||
Alert.alert('Erfolg', `Das Team "${teamName}" wurde erfolgreich erstellt.`);
|
||||
|
||||
if (fetchingOrgs) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-5 m-2.5 shadow`}>
|
||||
<ActivityIndicator size="large" color={isDarkMode ? '#93C5FD' : '#0055FF'} />
|
||||
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center mt-2.5`}>Lade Organisationen...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Direkt zur Teamdetailseite navigieren, ohne auf OK zu warten
|
||||
if (onTeamCreated) {
|
||||
console.log('Navigiere zur neuen Teamseite:', team.id, team.name);
|
||||
onTeamCreated(team.id, team.name);
|
||||
}
|
||||
|
||||
if (organizations.length === 0) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-5 m-2.5 shadow`}>
|
||||
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center p-5`}>
|
||||
Sie haben keine Berechtigung, Teams zu erstellen, oder Sie gehören keiner Organisation an.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Formular zurücksetzen
|
||||
setTeamName('');
|
||||
setAllocatedCredits('');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Teams:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Erstellen des Teams aufgetreten.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView className="flex-1">
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-5 m-2.5 shadow`}>
|
||||
<Text className={`text-2xl font-bold mb-5 ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>Neues Team erstellen</Text>
|
||||
|
||||
<View className="mb-4">
|
||||
<Text className={`text-base font-medium mb-2 ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>Organisation</Text>
|
||||
<View className="flex-row flex-wrap mb-2.5">
|
||||
{organizations.map((org) => (
|
||||
<TouchableOpacity
|
||||
key={org.id}
|
||||
className={`${selectedOrgId === org.id ? (isDarkMode ? 'bg-blue-800' : 'bg-blue-600') : (isDarkMode ? 'bg-gray-700' : 'bg-gray-100')} py-2.5 px-4 rounded-lg mr-2.5 mb-2.5`}
|
||||
onPress={() => setSelectedOrgId(org.id)}
|
||||
>
|
||||
<Text
|
||||
className={`${selectedOrgId === org.id ? 'text-white' : (isDarkMode ? 'text-gray-300' : 'text-gray-700')} font-medium`}
|
||||
>
|
||||
{org.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
if (!session) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-5 shadow`}>
|
||||
<Text
|
||||
className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} p-5 text-center`}
|
||||
>
|
||||
Bitte melden Sie sich an, um Teams zu erstellen.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
<View className="mb-4">
|
||||
<Text className={`text-base font-medium mb-2 ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>Teamname</Text>
|
||||
<TextInput
|
||||
className={`h-12 border ${isDarkMode ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-300 bg-gray-50 text-gray-800'} rounded-lg px-4 text-base`}
|
||||
value={teamName}
|
||||
onChangeText={setTeamName}
|
||||
placeholder="Name des Teams eingeben"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#9CA3AF'}
|
||||
/>
|
||||
</View>
|
||||
if (fetchingOrgs) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-5 shadow`}>
|
||||
<ActivityIndicator size="large" color={isDarkMode ? '#93C5FD' : '#0055FF'} />
|
||||
<Text
|
||||
className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} mt-2.5 text-center`}
|
||||
>
|
||||
Lade Organisationen...
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
<View className="mb-4">
|
||||
<Text className={`text-base font-medium mb-2 ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>Zugewiesene Kredite</Text>
|
||||
<TextInput
|
||||
className={`h-12 border ${isDarkMode ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-300 bg-gray-50 text-gray-800'} rounded-lg px-4 text-base`}
|
||||
value={allocatedCredits}
|
||||
onChangeText={setAllocatedCredits}
|
||||
placeholder="Anzahl der Kredite eingeben"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#9CA3AF'}
|
||||
keyboardType="number-pad"
|
||||
/>
|
||||
</View>
|
||||
if (organizations.length === 0) {
|
||||
return (
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-5 shadow`}>
|
||||
<Text
|
||||
className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} p-5 text-center`}
|
||||
>
|
||||
Sie haben keine Berechtigung, Teams zu erstellen, oder Sie gehören keiner Organisation an.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
<TouchableOpacity
|
||||
className={`h-12 rounded-lg justify-center items-center mt-2.5 ${loading ? (isDarkMode ? 'bg-gray-600' : 'bg-gray-400') : (isDarkMode ? 'bg-blue-700' : 'bg-blue-600')}`}
|
||||
onPress={createTeam}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator color="#fff" size="small" />
|
||||
) : (
|
||||
<Text className="text-white text-base font-bold">Team erstellen</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
return (
|
||||
<ScrollView className="flex-1">
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-5 shadow`}>
|
||||
<Text
|
||||
className={`mb-5 text-2xl font-bold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
Neues Team erstellen
|
||||
</Text>
|
||||
|
||||
<View className="mb-4">
|
||||
<Text
|
||||
className={`mb-2 text-base font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
Organisation
|
||||
</Text>
|
||||
<View className="mb-2.5 flex-row flex-wrap">
|
||||
{organizations.map((org) => (
|
||||
<TouchableOpacity
|
||||
key={org.id}
|
||||
className={`${selectedOrgId === org.id ? (isDarkMode ? 'bg-blue-800' : 'bg-blue-600') : isDarkMode ? 'bg-gray-700' : 'bg-gray-100'} mb-2.5 mr-2.5 rounded-lg px-4 py-2.5`}
|
||||
onPress={() => setSelectedOrgId(org.id)}
|
||||
>
|
||||
<Text
|
||||
className={`${selectedOrgId === org.id ? 'text-white' : isDarkMode ? 'text-gray-300' : 'text-gray-700'} font-medium`}
|
||||
>
|
||||
{org.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="mb-4">
|
||||
<Text
|
||||
className={`mb-2 text-base font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
Teamname
|
||||
</Text>
|
||||
<TextInput
|
||||
className={`h-12 border ${isDarkMode ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-300 bg-gray-50 text-gray-800'} rounded-lg px-4 text-base`}
|
||||
value={teamName}
|
||||
onChangeText={setTeamName}
|
||||
placeholder="Name des Teams eingeben"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#9CA3AF'}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View className="mb-4">
|
||||
<Text
|
||||
className={`mb-2 text-base font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}
|
||||
>
|
||||
Zugewiesene Kredite
|
||||
</Text>
|
||||
<TextInput
|
||||
className={`h-12 border ${isDarkMode ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-300 bg-gray-50 text-gray-800'} rounded-lg px-4 text-base`}
|
||||
value={allocatedCredits}
|
||||
onChangeText={setAllocatedCredits}
|
||||
placeholder="Anzahl der Kredite eingeben"
|
||||
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#9CA3AF'}
|
||||
keyboardType="number-pad"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
className={`mt-2.5 h-12 items-center justify-center rounded-lg ${loading ? (isDarkMode ? 'bg-gray-600' : 'bg-gray-400') : isDarkMode ? 'bg-blue-700' : 'bg-blue-600'}`}
|
||||
onPress={createTeam}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator color="#fff" size="small" />
|
||||
) : (
|
||||
<Text className="text-base font-bold text-white">Team erstellen</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
// NativeWind wird für das Styling verwendet, daher sind keine StyleSheet-Definitionen erforderlich
|
||||
|
|
|
|||
|
|
@ -7,159 +7,194 @@ import { Session } from '@supabase/supabase-js';
|
|||
import { useTheme, lightColors, darkColors } from '../utils/themeContext';
|
||||
|
||||
export default function DashboardStats() {
|
||||
const router = useRouter();
|
||||
const { isDarkMode } = useTheme();
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [teamCount, setTeamCount] = useState(0);
|
||||
const [orgCount, setOrgCount] = useState(0);
|
||||
const [availableMana, setAvailableMana] = useState(0);
|
||||
const [totalMana, setTotalMana] = useState(0);
|
||||
const router = useRouter();
|
||||
const { isDarkMode } = useTheme();
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [teamCount, setTeamCount] = useState(0);
|
||||
const [orgCount, setOrgCount] = useState(0);
|
||||
const [availableMana, setAvailableMana] = useState(0);
|
||||
const [totalMana, setTotalMana] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
// Prüfe den aktuellen Authentifizierungsstatus
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserStats(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
// Prüfe den aktuellen Authentifizierungsstatus
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserStats(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Abonniere Authentifizierungsänderungen
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserStats(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
// Abonniere Authentifizierungsänderungen
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserStats(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
async function fetchUserStats(userId: string) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Hole alle Teams, in denen der Benutzer Mitglied ist
|
||||
const { data: teamMembers, error: teamMembersError } = await supabase
|
||||
.from('team_members')
|
||||
.select('team_id')
|
||||
.eq('user_id', userId);
|
||||
async function fetchUserStats(userId: string) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
if (teamMembersError) throw teamMembersError;
|
||||
|
||||
setTeamCount(teamMembers?.length || 0);
|
||||
// Hole alle Teams, in denen der Benutzer Mitglied ist
|
||||
const { data: teamMembers, error: teamMembersError } = await supabase
|
||||
.from('team_members')
|
||||
.select('team_id')
|
||||
.eq('user_id', userId);
|
||||
|
||||
// Hole alle Organisationen, in denen der Benutzer eine Rolle hat
|
||||
const { data: userRoles, error: userRolesError } = await supabase
|
||||
.from('user_roles')
|
||||
.select('organization_id')
|
||||
.eq('user_id', userId)
|
||||
.not('organization_id', 'is', null);
|
||||
if (teamMembersError) throw teamMembersError;
|
||||
|
||||
if (userRolesError) throw userRolesError;
|
||||
|
||||
// Entferne Duplikate (falls der Benutzer mehrere Rollen in einer Organisation hat)
|
||||
const uniqueOrgIds = [...new Set(userRoles?.map(role => role.organization_id) || [])];
|
||||
setOrgCount(uniqueOrgIds.length);
|
||||
|
||||
// Hole die Mana-Informationen aus dem Profil des Benutzers
|
||||
const { data: profileData, error: profileError } = await supabase
|
||||
.from('profiles')
|
||||
.select('individual_quota, individual_usage')
|
||||
.eq('id', userId)
|
||||
.single();
|
||||
|
||||
if (profileError) throw profileError;
|
||||
|
||||
if (profileData) {
|
||||
const quota = profileData.individual_quota || 0;
|
||||
const usage = profileData.individual_usage || 0;
|
||||
const available = Math.max(0, quota - usage);
|
||||
|
||||
setTotalMana(quota);
|
||||
setAvailableMana(available);
|
||||
}
|
||||
setTeamCount(teamMembers?.length || 0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Benutzerstatistiken:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
// Hole alle Organisationen, in denen der Benutzer eine Rolle hat
|
||||
const { data: userRoles, error: userRolesError } = await supabase
|
||||
.from('user_roles')
|
||||
.select('organization_id')
|
||||
.eq('user_id', userId)
|
||||
.not('organization_id', 'is', null);
|
||||
|
||||
if (!session || loading) {
|
||||
return (
|
||||
<View className={`flex-row justify-between ${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-4 mb-5 shadow`}>
|
||||
<ActivityIndicator size="small" color={isDarkMode ? '#60A5FA' : '#0055FF'} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (userRolesError) throw userRolesError;
|
||||
|
||||
return (
|
||||
<View className="mb-5">
|
||||
{/* Mana-Anzeige */}
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-4 mb-3 shadow`}>
|
||||
<TouchableOpacity
|
||||
className="items-center"
|
||||
onPress={() => router.push('/get-mana')}
|
||||
>
|
||||
<View className="flex-row items-center mb-2">
|
||||
<FontAwesome5 name="fire" size={18} color="#F59E0B" className="mr-2" />
|
||||
<Text className={`text-lg font-bold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>Verfügbares Mana</Text>
|
||||
</View>
|
||||
<View className="w-full bg-gray-200 rounded-full h-4 mb-2 dark:bg-gray-700">
|
||||
<View
|
||||
className="h-4 rounded-full"
|
||||
style={{
|
||||
width: totalMana > 0 ? `${Math.min(100, (availableMana / totalMana) * 100)}%` : '0%',
|
||||
backgroundColor: isDarkMode ? darkColors.primary : lightColors.primary
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View className="flex-row justify-between w-full">
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>Verfügbar: {availableMana}</Text>
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>Gesamt: {totalMana}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Teams und Organisationen */}
|
||||
<View className={`flex-row justify-between ${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-4 shadow`}>
|
||||
<TouchableOpacity
|
||||
className={`flex-1 flex-row items-center justify-center ${isDarkMode ? 'bg-gray-700' : 'bg-gray-50'} rounded-lg p-3 mr-2`}
|
||||
onPress={() => router.push('/teams')}
|
||||
>
|
||||
<View className="items-center">
|
||||
<View className="flex-row items-center mb-1">
|
||||
<FontAwesome5 name="users" size={16} color={isDarkMode ? '#60A5FA' : '#0055FF'} className="mr-2" />
|
||||
<Text className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>Teams</Text>
|
||||
</View>
|
||||
<View className={`${isDarkMode ? 'bg-blue-900' : 'bg-blue-100'} px-3 py-1 rounded-full`}>
|
||||
<Text className={`${isDarkMode ? 'text-blue-300' : 'text-blue-700'} font-medium`}>{teamCount}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
className={`flex-1 flex-row items-center justify-center ${isDarkMode ? 'bg-gray-700' : 'bg-gray-50'} rounded-lg p-3 ml-2`}
|
||||
onPress={() => router.push('/organizations')}
|
||||
>
|
||||
<View className="items-center">
|
||||
<View className="flex-row items-center mb-1">
|
||||
<FontAwesome5 name="building" size={16} color={isDarkMode ? '#60A5FA' : '#0055FF'} className="mr-2" />
|
||||
<Text className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>Organisationen</Text>
|
||||
</View>
|
||||
<View className={`${isDarkMode ? 'bg-blue-900' : 'bg-blue-100'} px-3 py-1 rounded-full`}>
|
||||
<Text className={`${isDarkMode ? 'text-blue-300' : 'text-blue-700'} font-medium`}>{orgCount}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
// Entferne Duplikate (falls der Benutzer mehrere Rollen in einer Organisation hat)
|
||||
const uniqueOrgIds = [...new Set(userRoles?.map((role) => role.organization_id) || [])];
|
||||
setOrgCount(uniqueOrgIds.length);
|
||||
|
||||
// Hole die Mana-Informationen aus dem Profil des Benutzers
|
||||
const { data: profileData, error: profileError } = await supabase
|
||||
.from('profiles')
|
||||
.select('individual_quota, individual_usage')
|
||||
.eq('id', userId)
|
||||
.single();
|
||||
|
||||
if (profileError) throw profileError;
|
||||
|
||||
if (profileData) {
|
||||
const quota = profileData.individual_quota || 0;
|
||||
const usage = profileData.individual_usage || 0;
|
||||
const available = Math.max(0, quota - usage);
|
||||
|
||||
setTotalMana(quota);
|
||||
setAvailableMana(available);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Benutzerstatistiken:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!session || loading) {
|
||||
return (
|
||||
<View
|
||||
className={`flex-row justify-between ${isDarkMode ? 'bg-gray-800' : 'bg-white'} mb-5 rounded-lg p-4 shadow`}
|
||||
>
|
||||
<ActivityIndicator size="small" color={isDarkMode ? '#60A5FA' : '#0055FF'} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="mb-5">
|
||||
{/* Mana-Anzeige */}
|
||||
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} mb-3 rounded-lg p-4 shadow`}>
|
||||
<TouchableOpacity className="items-center" onPress={() => router.push('/get-mana')}>
|
||||
<View className="mb-2 flex-row items-center">
|
||||
<FontAwesome5 name="fire" size={18} color="#F59E0B" className="mr-2" />
|
||||
<Text className={`text-lg font-bold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>
|
||||
Verfügbares Mana
|
||||
</Text>
|
||||
</View>
|
||||
<View className="mb-2 h-4 w-full rounded-full bg-gray-200 dark:bg-gray-700">
|
||||
<View
|
||||
className="h-4 rounded-full"
|
||||
style={{
|
||||
width:
|
||||
totalMana > 0 ? `${Math.min(100, (availableMana / totalMana) * 100)}%` : '0%',
|
||||
backgroundColor: isDarkMode ? darkColors.primary : lightColors.primary,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View className="w-full flex-row justify-between">
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
|
||||
Verfügbar: {availableMana}
|
||||
</Text>
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
|
||||
Gesamt: {totalMana}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Teams und Organisationen */}
|
||||
<View
|
||||
className={`flex-row justify-between ${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-4 shadow`}
|
||||
>
|
||||
<TouchableOpacity
|
||||
className={`flex-1 flex-row items-center justify-center ${isDarkMode ? 'bg-gray-700' : 'bg-gray-50'} mr-2 rounded-lg p-3`}
|
||||
onPress={() => router.push('/teams')}
|
||||
>
|
||||
<View className="items-center">
|
||||
<View className="mb-1 flex-row items-center">
|
||||
<FontAwesome5
|
||||
name="users"
|
||||
size={16}
|
||||
color={isDarkMode ? '#60A5FA' : '#0055FF'}
|
||||
className="mr-2"
|
||||
/>
|
||||
<Text
|
||||
className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
Teams
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`${isDarkMode ? 'bg-blue-900' : 'bg-blue-100'} rounded-full px-3 py-1`}
|
||||
>
|
||||
<Text className={`${isDarkMode ? 'text-blue-300' : 'text-blue-700'} font-medium`}>
|
||||
{teamCount}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
className={`flex-1 flex-row items-center justify-center ${isDarkMode ? 'bg-gray-700' : 'bg-gray-50'} ml-2 rounded-lg p-3`}
|
||||
onPress={() => router.push('/organizations')}
|
||||
>
|
||||
<View className="items-center">
|
||||
<View className="mb-1 flex-row items-center">
|
||||
<FontAwesome5
|
||||
name="building"
|
||||
size={16}
|
||||
color={isDarkMode ? '#60A5FA' : '#0055FF'}
|
||||
className="mr-2"
|
||||
/>
|
||||
<Text
|
||||
className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
Organisationen
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`${isDarkMode ? 'bg-blue-900' : 'bg-blue-100'} rounded-full px-3 py-1`}
|
||||
>
|
||||
<Text className={`${isDarkMode ? 'text-blue-300' : 'text-blue-700'} font-medium`}>
|
||||
{orgCount}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import { Text, View } from 'react-native';
|
||||
|
||||
export const EditScreenInfo = ({ path }: { path: string }) => {
|
||||
const title = 'Open up the code for this screen:';
|
||||
const description =
|
||||
'Change any of the text, save the file, and your app will automatically update.';
|
||||
const title = 'Open up the code for this screen:';
|
||||
const description =
|
||||
'Change any of the text, save the file, and your app will automatically update.';
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View className={styles.getStartedContainer}>
|
||||
<Text className={styles.getStartedText}>{title}</Text>
|
||||
<View className={styles.codeHighlightContainer + styles.homeScreenFilename}>
|
||||
<Text>{path}</Text>
|
||||
</View>
|
||||
<Text className={styles.getStartedText}>{description}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View>
|
||||
<View className={styles.getStartedContainer}>
|
||||
<Text className={styles.getStartedText}>{title}</Text>
|
||||
<View className={styles.codeHighlightContainer + styles.homeScreenFilename}>
|
||||
<Text>{path}</Text>
|
||||
</View>
|
||||
<Text className={styles.getStartedText}>{description}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
codeHighlightContainer: `rounded-md px-1`,
|
||||
getStartedContainer: `items-center mx-12`,
|
||||
getStartedText: `text-lg leading-6 text-center`,
|
||||
helpContainer: `items-center mx-5 mt-4`,
|
||||
helpLink: `py-4`,
|
||||
helpLinkText: `text-center`,
|
||||
homeScreenFilename: `my-2`,
|
||||
codeHighlightContainer: `rounded-md px-1`,
|
||||
getStartedContainer: `items-center mx-12`,
|
||||
getStartedText: `text-lg leading-6 text-center`,
|
||||
helpContainer: `items-center mx-5 mt-4`,
|
||||
helpLink: `py-4`,
|
||||
helpLinkText: `text-center`,
|
||||
homeScreenFilename: `my-2`,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,29 +3,29 @@ import { forwardRef } from 'react';
|
|||
import { Pressable, StyleSheet } from 'react-native';
|
||||
|
||||
export const HeaderButton = forwardRef<typeof Pressable, { onPress?: () => void }>(
|
||||
({ onPress }, ref) => {
|
||||
return (
|
||||
<Pressable onPress={onPress}>
|
||||
{({ pressed }) => (
|
||||
<FontAwesome
|
||||
name="info-circle"
|
||||
size={25}
|
||||
color="gray"
|
||||
style={[
|
||||
styles.headerRight,
|
||||
{
|
||||
opacity: pressed ? 0.5 : 1,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
({ onPress }, ref) => {
|
||||
return (
|
||||
<Pressable onPress={onPress}>
|
||||
{({ pressed }) => (
|
||||
<FontAwesome
|
||||
name="info-circle"
|
||||
size={25}
|
||||
color="gray"
|
||||
style={[
|
||||
styles.headerRight,
|
||||
{
|
||||
opacity: pressed ? 0.5 : 1,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
headerRight: {
|
||||
marginRight: 15,
|
||||
},
|
||||
headerRight: {
|
||||
marginRight: 15,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,296 +7,381 @@ import { useTheme } from '../utils/themeContext';
|
|||
import { useRouter } from 'expo-router';
|
||||
|
||||
interface Organization {
|
||||
id: string;
|
||||
name: string;
|
||||
total_credits: number;
|
||||
used_credits: number;
|
||||
created_at: string;
|
||||
team_count?: number;
|
||||
user_role?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
total_credits: number;
|
||||
used_credits: number;
|
||||
created_at: string;
|
||||
team_count?: number;
|
||||
user_role?: string;
|
||||
}
|
||||
|
||||
interface OrganizationListProps {
|
||||
hideTitle?: boolean;
|
||||
hideTitle?: boolean;
|
||||
}
|
||||
|
||||
// Definiere die Ref-Schnittstelle
|
||||
interface OrganizationListRef {
|
||||
refreshOrganizations: () => void;
|
||||
refreshOrganizations: () => void;
|
||||
}
|
||||
|
||||
const OrganizationList = forwardRef<OrganizationListRef, OrganizationListProps>(({ hideTitle = false }, ref) => {
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||
const { isDarkMode } = useTheme();
|
||||
const router = useRouter();
|
||||
const OrganizationList = forwardRef<OrganizationListRef, OrganizationListProps>(
|
||||
({ hideTitle = false }, ref) => {
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||
const { isDarkMode } = useTheme();
|
||||
const router = useRouter();
|
||||
|
||||
// Stelle die refreshOrganizations-Methode über die Ref zur Verfügung
|
||||
useImperativeHandle(ref, () => ({
|
||||
refreshOrganizations: () => {
|
||||
if (session) {
|
||||
console.log('Aktualisiere Organisationsliste');
|
||||
fetchUserOrganizations(session.user.id);
|
||||
}
|
||||
}
|
||||
}));
|
||||
// Stelle die refreshOrganizations-Methode über die Ref zur Verfügung
|
||||
useImperativeHandle(ref, () => ({
|
||||
refreshOrganizations: () => {
|
||||
if (session) {
|
||||
console.log('Aktualisiere Organisationsliste');
|
||||
fetchUserOrganizations(session.user.id);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
// Prüfe den aktuellen Authentifizierungsstatus
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserOrganizations(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
// Prüfe den aktuellen Authentifizierungsstatus
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserOrganizations(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Abonniere Authentifizierungsänderungen
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserOrganizations(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
// Abonniere Authentifizierungsänderungen
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserOrganizations(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
async function fetchUserOrganizations(userId: string) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Hole alle Organisationen, in denen der Benutzer eine Rolle hat
|
||||
const { data: userRoles, error: userRolesError } = await supabase
|
||||
.from('user_roles')
|
||||
.select('organization_id, role_id, roles(name)')
|
||||
.eq('user_id', userId)
|
||||
.not('organization_id', 'is', null) as {
|
||||
data: Array<{
|
||||
organization_id: string;
|
||||
role_id: string;
|
||||
roles: { name: string }
|
||||
}> | null;
|
||||
error: any
|
||||
};
|
||||
async function fetchUserOrganizations(userId: string) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
if (userRolesError) throw userRolesError;
|
||||
// Hole alle Organisationen, in denen der Benutzer eine Rolle hat
|
||||
const { data: userRoles, error: userRolesError } = (await supabase
|
||||
.from('user_roles')
|
||||
.select('organization_id, role_id, roles(name)')
|
||||
.eq('user_id', userId)
|
||||
.not('organization_id', 'is', null)) as {
|
||||
data: Array<{
|
||||
organization_id: string;
|
||||
role_id: string;
|
||||
roles: { name: string };
|
||||
}> | null;
|
||||
error: any;
|
||||
};
|
||||
|
||||
if (userRoles && userRoles.length > 0) {
|
||||
// Extrahiere die Organisations-IDs
|
||||
const orgIds = [...new Set(userRoles.map(role => role.organization_id))];
|
||||
if (userRolesError) throw userRolesError;
|
||||
|
||||
// Hole die Organisations-Details
|
||||
const { data: orgsData, error: orgsError } = await supabase
|
||||
.from('organizations')
|
||||
.select('id, name, total_credits, used_credits, created_at')
|
||||
.in('id', orgIds);
|
||||
if (userRoles && userRoles.length > 0) {
|
||||
// Extrahiere die Organisations-IDs
|
||||
const orgIds = [...new Set(userRoles.map((role) => role.organization_id))];
|
||||
|
||||
if (orgsError) throw orgsError;
|
||||
// Hole die Organisations-Details
|
||||
const { data: orgsData, error: orgsError } = await supabase
|
||||
.from('organizations')
|
||||
.select('id, name, total_credits, used_credits, created_at')
|
||||
.in('id', orgIds);
|
||||
|
||||
if (orgsData && orgsData.length > 0) {
|
||||
// Hole die Anzahl der Teams für jede Organisation
|
||||
const orgsWithTeamCount = await Promise.all(
|
||||
orgsData.map(async (org) => {
|
||||
// Finde die Rolle des Benutzers in dieser Organisation
|
||||
const userRolesInOrg = userRoles.filter(role => role.organization_id === org.id);
|
||||
const highestRole = findHighestRole(userRolesInOrg);
|
||||
|
||||
// Hole die Anzahl der Teams in dieser Organisation
|
||||
const { count, error: teamCountError } = await supabase
|
||||
.from('teams')
|
||||
.select('id', { count: 'exact', head: true })
|
||||
.eq('organization_id', org.id);
|
||||
if (orgsError) throw orgsError;
|
||||
|
||||
if (teamCountError) {
|
||||
console.error('Fehler beim Abrufen der Team-Anzahl:', teamCountError);
|
||||
return {
|
||||
...org,
|
||||
team_count: 0,
|
||||
user_role: highestRole
|
||||
};
|
||||
}
|
||||
if (orgsData && orgsData.length > 0) {
|
||||
// Hole die Anzahl der Teams für jede Organisation
|
||||
const orgsWithTeamCount = await Promise.all(
|
||||
orgsData.map(async (org) => {
|
||||
// Finde die Rolle des Benutzers in dieser Organisation
|
||||
const userRolesInOrg = userRoles.filter((role) => role.organization_id === org.id);
|
||||
const highestRole = findHighestRole(userRolesInOrg);
|
||||
|
||||
return {
|
||||
...org,
|
||||
team_count: count || 0,
|
||||
user_role: highestRole
|
||||
};
|
||||
})
|
||||
);
|
||||
// Hole die Anzahl der Teams in dieser Organisation
|
||||
const { count, error: teamCountError } = await supabase
|
||||
.from('teams')
|
||||
.select('id', { count: 'exact', head: true })
|
||||
.eq('organization_id', org.id);
|
||||
|
||||
setOrganizations(orgsWithTeamCount);
|
||||
} else {
|
||||
setOrganizations([]);
|
||||
}
|
||||
} else {
|
||||
setOrganizations([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Organisationen:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Laden Ihrer Organisationen aufgetreten.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
if (teamCountError) {
|
||||
console.error('Fehler beim Abrufen der Team-Anzahl:', teamCountError);
|
||||
return {
|
||||
...org,
|
||||
team_count: 0,
|
||||
user_role: highestRole,
|
||||
};
|
||||
}
|
||||
|
||||
// Hilfsfunktion, um die höchste Rolle eines Benutzers zu finden
|
||||
function findHighestRole(userRoles: Array<{ role_id: string; roles: { name: string } }>): string {
|
||||
const roleHierarchy = {
|
||||
'system_admin': 4,
|
||||
'org_admin': 3,
|
||||
'team_admin': 2,
|
||||
'member': 1
|
||||
};
|
||||
return {
|
||||
...org,
|
||||
team_count: count || 0,
|
||||
user_role: highestRole,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
let highestRoleName = 'member';
|
||||
let highestRoleValue = 0;
|
||||
setOrganizations(orgsWithTeamCount);
|
||||
} else {
|
||||
setOrganizations([]);
|
||||
}
|
||||
} else {
|
||||
setOrganizations([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Organisationen:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Laden Ihrer Organisationen aufgetreten.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
userRoles.forEach(role => {
|
||||
const roleName = role.roles?.name;
|
||||
if (roleName && roleHierarchy[roleName as keyof typeof roleHierarchy] > highestRoleValue) {
|
||||
highestRoleName = roleName;
|
||||
highestRoleValue = roleHierarchy[roleName as keyof typeof roleHierarchy];
|
||||
}
|
||||
});
|
||||
// Hilfsfunktion, um die höchste Rolle eines Benutzers zu finden
|
||||
function findHighestRole(
|
||||
userRoles: Array<{ role_id: string; roles: { name: string } }>
|
||||
): string {
|
||||
const roleHierarchy = {
|
||||
system_admin: 4,
|
||||
org_admin: 3,
|
||||
team_admin: 2,
|
||||
member: 1,
|
||||
};
|
||||
|
||||
return highestRoleName;
|
||||
}
|
||||
let highestRoleName = 'member';
|
||||
let highestRoleValue = 0;
|
||||
|
||||
// Keine Aufklappfunktion mehr benötigt
|
||||
userRoles.forEach((role) => {
|
||||
const roleName = role.roles?.name;
|
||||
if (roleName && roleHierarchy[roleName as keyof typeof roleHierarchy] > highestRoleValue) {
|
||||
highestRoleName = roleName;
|
||||
highestRoleValue = roleHierarchy[roleName as keyof typeof roleHierarchy];
|
||||
}
|
||||
});
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
return highestRoleName;
|
||||
}
|
||||
|
||||
// Diese Funktion wird nicht mehr benötigt, da wir direkt mit NativeWind-Klassen arbeiten
|
||||
// Keine Aufklappfunktion mehr benötigt
|
||||
|
||||
const getRoleName = (role: string) => {
|
||||
switch (role) {
|
||||
case 'system_admin':
|
||||
return 'System-Admin';
|
||||
case 'org_admin':
|
||||
return 'Organisations-Admin';
|
||||
case 'team_admin':
|
||||
return 'Team-Admin';
|
||||
default:
|
||||
return 'Mitglied';
|
||||
}
|
||||
};
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
};
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<View className={`flex-1 ${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-4 m-2.5 shadow`}>
|
||||
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center p-5`}>
|
||||
Bitte melden Sie sich an, um Ihre Organisationen zu sehen.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Diese Funktion wird nicht mehr benötigt, da wir direkt mit NativeWind-Klassen arbeiten
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View className={`flex-1 ${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-4 m-2.5 shadow`}>
|
||||
<ActivityIndicator size="large" color={isDarkMode ? '#93C5FD' : '#0055FF'} />
|
||||
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center mt-2.5`}>Lade Organisationen...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
const getRoleName = (role: string) => {
|
||||
switch (role) {
|
||||
case 'system_admin':
|
||||
return 'System-Admin';
|
||||
case 'org_admin':
|
||||
return 'Organisations-Admin';
|
||||
case 'team_admin':
|
||||
return 'Team-Admin';
|
||||
default:
|
||||
return 'Mitglied';
|
||||
}
|
||||
};
|
||||
|
||||
if (organizations.length === 0) {
|
||||
return (
|
||||
<View className={`flex-1 ${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-4 m-2.5 shadow`}>
|
||||
<FontAwesome5 name="building" size={50} color={isDarkMode ? '#4B5563' : '#ccc'} className="self-center mb-4" />
|
||||
<Text className={`text-lg font-medium ${isDarkMode ? 'text-gray-100' : 'text-gray-800'} text-center mb-2.5`}>
|
||||
Sie gehören derzeit keiner Organisation an.
|
||||
</Text>
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center px-5`}>
|
||||
Erstellen Sie eine neue Organisation oder bitten Sie einen Administrator, Sie einer Organisation hinzuzufügen.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (!session) {
|
||||
return (
|
||||
<View
|
||||
className={`flex-1 ${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-4 shadow`}
|
||||
>
|
||||
<Text
|
||||
className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} p-5 text-center`}
|
||||
>
|
||||
Bitte melden Sie sich an, um Ihre Organisationen zu sehen.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View className={`flex-1 ${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-4 m-2.5 shadow`}>
|
||||
{!hideTitle && (
|
||||
<Text className={`text-2xl font-bold mb-4 ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>Meine Organisationen</Text>
|
||||
)}
|
||||
<FlatList
|
||||
data={organizations}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
console.log('Organisation angeklickt:', item.id, item.name);
|
||||
router.push({
|
||||
pathname: '/organizations/[id]',
|
||||
params: { id: item.id, name: item.name }
|
||||
});
|
||||
}}
|
||||
className={`${isDarkMode ? 'bg-gray-700' : 'bg-gray-50'} rounded-lg p-4 mb-2.5 shadow-sm`}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{/* Header mit Organisationsname */}
|
||||
<View className="flex-row justify-between items-center mb-3">
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5 name="building" size={18} color={isDarkMode ? '#93C5FD' : '#0055FF'} className="mr-2.5" />
|
||||
<Text className={`text-lg font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>{item.name}</Text>
|
||||
</View>
|
||||
<View className={`px-2 py-0.5 rounded-full ${item.user_role === 'system_admin' ? 'bg-red-600' : item.user_role === 'org_admin' ? 'bg-orange-500' : item.user_role === 'team_admin' ? 'bg-green-600' : 'bg-gray-600'}`}>
|
||||
<Text className="text-xs font-medium text-white">{getRoleName(item.user_role || 'member')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Mittlerer Bereich mit Teams und Erstellungsdatum */}
|
||||
<View className="flex-row justify-between items-center mb-3">
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5 name="users" size={14} color={isDarkMode ? '#9CA3AF' : '#666'} className="mr-2" />
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}>{item.team_count} {item.team_count === 1 ? 'Team' : 'Teams'}</Text>
|
||||
</View>
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5 name="calendar-alt" size={14} color={isDarkMode ? '#9CA3AF' : '#666'} className="mr-2" />
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}>{formatDate(item.created_at)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Unterer Bereich mit Kreditinformationen */}
|
||||
<View className={`flex-row justify-between mt-1 pt-2 border-t ${isDarkMode ? 'border-gray-600' : 'border-gray-200'}`}>
|
||||
<View className="flex-1 items-center">
|
||||
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}>Gesamte Kredite</Text>
|
||||
<Text className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>{item.total_credits}</Text>
|
||||
</View>
|
||||
|
||||
<View className={`flex-1 items-center border-x ${isDarkMode ? 'border-gray-600' : 'border-gray-200'}`}>
|
||||
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}>Verwendete Kredite</Text>
|
||||
<Text className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>{item.used_credits}</Text>
|
||||
</View>
|
||||
|
||||
<View className="flex-1 items-center">
|
||||
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}>Verfügbar</Text>
|
||||
<Text
|
||||
className={`text-base font-semibold ${item.total_credits - item.used_credits > 0 ? (isDarkMode ? 'text-green-400' : 'text-green-600') : (isDarkMode ? 'text-red-400' : 'text-red-600')}`}
|
||||
>
|
||||
{item.total_credits - item.used_credits}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
className="pb-5"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
if (loading) {
|
||||
return (
|
||||
<View
|
||||
className={`flex-1 ${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-4 shadow`}
|
||||
>
|
||||
<ActivityIndicator size="large" color={isDarkMode ? '#93C5FD' : '#0055FF'} />
|
||||
<Text
|
||||
className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} mt-2.5 text-center`}
|
||||
>
|
||||
Lade Organisationen...
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (organizations.length === 0) {
|
||||
return (
|
||||
<View
|
||||
className={`flex-1 ${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-4 shadow`}
|
||||
>
|
||||
<FontAwesome5
|
||||
name="building"
|
||||
size={50}
|
||||
color={isDarkMode ? '#4B5563' : '#ccc'}
|
||||
className="mb-4 self-center"
|
||||
/>
|
||||
<Text
|
||||
className={`text-lg font-medium ${isDarkMode ? 'text-gray-100' : 'text-gray-800'} mb-2.5 text-center`}
|
||||
>
|
||||
Sie gehören derzeit keiner Organisation an.
|
||||
</Text>
|
||||
<Text
|
||||
className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} px-5 text-center`}
|
||||
>
|
||||
Erstellen Sie eine neue Organisation oder bitten Sie einen Administrator, Sie einer
|
||||
Organisation hinzuzufügen.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
className={`flex-1 ${isDarkMode ? 'bg-gray-800' : 'bg-white'} m-2.5 rounded-lg p-4 shadow`}
|
||||
>
|
||||
{!hideTitle && (
|
||||
<Text
|
||||
className={`mb-4 text-2xl font-bold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
Meine Organisationen
|
||||
</Text>
|
||||
)}
|
||||
<FlatList
|
||||
data={organizations}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
console.log('Organisation angeklickt:', item.id, item.name);
|
||||
router.push({
|
||||
pathname: '/organizations/[id]',
|
||||
params: { id: item.id, name: item.name },
|
||||
});
|
||||
}}
|
||||
className={`${isDarkMode ? 'bg-gray-700' : 'bg-gray-50'} mb-2.5 rounded-lg p-4 shadow-sm`}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{/* Header mit Organisationsname */}
|
||||
<View className="mb-3 flex-row items-center justify-between">
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5
|
||||
name="building"
|
||||
size={18}
|
||||
color={isDarkMode ? '#93C5FD' : '#0055FF'}
|
||||
className="mr-2.5"
|
||||
/>
|
||||
<Text
|
||||
className={`text-lg font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`rounded-full px-2 py-0.5 ${item.user_role === 'system_admin' ? 'bg-red-600' : item.user_role === 'org_admin' ? 'bg-orange-500' : item.user_role === 'team_admin' ? 'bg-green-600' : 'bg-gray-600'}`}
|
||||
>
|
||||
<Text className="text-xs font-medium text-white">
|
||||
{getRoleName(item.user_role || 'member')}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Mittlerer Bereich mit Teams und Erstellungsdatum */}
|
||||
<View className="mb-3 flex-row items-center justify-between">
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5
|
||||
name="users"
|
||||
size={14}
|
||||
color={isDarkMode ? '#9CA3AF' : '#666'}
|
||||
className="mr-2"
|
||||
/>
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}>
|
||||
{item.team_count} {item.team_count === 1 ? 'Team' : 'Teams'}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5
|
||||
name="calendar-alt"
|
||||
size={14}
|
||||
color={isDarkMode ? '#9CA3AF' : '#666'}
|
||||
className="mr-2"
|
||||
/>
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}>
|
||||
{formatDate(item.created_at)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Unterer Bereich mit Kreditinformationen */}
|
||||
<View
|
||||
className={`mt-1 flex-row justify-between border-t pt-2 ${isDarkMode ? 'border-gray-600' : 'border-gray-200'}`}
|
||||
>
|
||||
<View className="flex-1 items-center">
|
||||
<Text
|
||||
className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}
|
||||
>
|
||||
Gesamte Kredite
|
||||
</Text>
|
||||
<Text
|
||||
className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
{item.total_credits}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
className={`flex-1 items-center border-x ${isDarkMode ? 'border-gray-600' : 'border-gray-200'}`}
|
||||
>
|
||||
<Text
|
||||
className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}
|
||||
>
|
||||
Verwendete Kredite
|
||||
</Text>
|
||||
<Text
|
||||
className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
{item.used_credits}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className="flex-1 items-center">
|
||||
<Text
|
||||
className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}
|
||||
>
|
||||
Verfügbar
|
||||
</Text>
|
||||
<Text
|
||||
className={`text-base font-semibold ${item.total_credits - item.used_credits > 0 ? (isDarkMode ? 'text-green-400' : 'text-green-600') : isDarkMode ? 'text-red-400' : 'text-red-600'}`}
|
||||
>
|
||||
{item.total_credits - item.used_credits}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
className="pb-5"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// NativeWind wird für das Styling verwendet, daher sind keine StyleSheet-Definitionen erforderlich
|
||||
|
||||
|
|
|
|||
|
|
@ -3,23 +3,23 @@ import { Text, View } from 'react-native';
|
|||
import { EditScreenInfo } from './EditScreenInfo';
|
||||
|
||||
type ScreenContentProps = {
|
||||
title: string;
|
||||
path: string;
|
||||
children?: React.ReactNode;
|
||||
title: string;
|
||||
path: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const ScreenContent = ({ title, path, children }: ScreenContentProps) => {
|
||||
return (
|
||||
<View className={styles.container}>
|
||||
<Text className={styles.title}>{title}</Text>
|
||||
<View className={styles.separator} />
|
||||
<EditScreenInfo path={path} />
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View className={styles.container}>
|
||||
<Text className={styles.title}>{title}</Text>
|
||||
<View className={styles.separator} />
|
||||
<EditScreenInfo path={path} />
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const styles = {
|
||||
container: `items-center flex-1 justify-center`,
|
||||
separator: `h-[1px] my-7 w-4/5 bg-gray-200`,
|
||||
title: `text-xl font-bold`,
|
||||
container: `items-center flex-1 justify-center`,
|
||||
separator: `h-[1px] my-7 w-4/5 bg-gray-200`,
|
||||
title: `text-xl font-bold`,
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,14 +2,14 @@ import FontAwesome from '@expo/vector-icons/FontAwesome';
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export const TabBarIcon = (props: {
|
||||
name: React.ComponentProps<typeof FontAwesome>['name'];
|
||||
color: string;
|
||||
name: React.ComponentProps<typeof FontAwesome>['name'];
|
||||
color: string;
|
||||
}) => {
|
||||
return <FontAwesome size={28} style={styles.tabBarIcon} {...props} />;
|
||||
return <FontAwesome size={28} style={styles.tabBarIcon} {...props} />;
|
||||
};
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
tabBarIcon: {
|
||||
marginBottom: -3,
|
||||
},
|
||||
tabBarIcon: {
|
||||
marginBottom: -3,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, FlatList, TouchableOpacity, ActivityIndicator, Alert, Platform } from 'react-native';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
TouchableOpacity,
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
|
|
@ -7,332 +15,401 @@ import { useRouter } from 'expo-router';
|
|||
import { useTheme } from '../utils/themeContext';
|
||||
|
||||
interface Team {
|
||||
id: string;
|
||||
name: string;
|
||||
organization_id: string;
|
||||
organization_name?: string;
|
||||
allocated_credits?: number;
|
||||
used_credits?: number;
|
||||
created_at: string;
|
||||
id: string;
|
||||
name: string;
|
||||
organization_id: string;
|
||||
organization_name?: string;
|
||||
allocated_credits?: number;
|
||||
used_credits?: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface TeamListProps {
|
||||
hideTitle?: boolean;
|
||||
hideTitle?: boolean;
|
||||
}
|
||||
|
||||
// Funktion zum Aktualisieren der Teamliste, die von außen aufgerufen werden kann
|
||||
export function refreshTeamList(userId: string, callback?: () => void) {
|
||||
if (!userId) return;
|
||||
|
||||
// Hole alle Teams, in denen der Benutzer Mitglied ist
|
||||
supabase
|
||||
.from('team_members')
|
||||
.select('team_id, allocated_credits, used_credits')
|
||||
.eq('user_id', userId)
|
||||
.then(({ data: teamMembers, error: teamMembersError }) => {
|
||||
if (teamMembersError) {
|
||||
console.error('Fehler beim Abrufen der Team-Mitglieder:', teamMembersError);
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
if (!userId) return;
|
||||
|
||||
if (teamMembers && teamMembers.length > 0) {
|
||||
// Extrahiere die Team-IDs
|
||||
const teamIds = teamMembers.map(member => member.team_id);
|
||||
// Hole alle Teams, in denen der Benutzer Mitglied ist
|
||||
supabase
|
||||
.from('team_members')
|
||||
.select('team_id, allocated_credits, used_credits')
|
||||
.eq('user_id', userId)
|
||||
.then(({ data: teamMembers, error: teamMembersError }) => {
|
||||
if (teamMembersError) {
|
||||
console.error('Fehler beim Abrufen der Team-Mitglieder:', teamMembersError);
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Hole die Team-Details
|
||||
supabase
|
||||
.from('teams')
|
||||
.select('id, name, organization_id, created_at')
|
||||
.in('id', teamIds)
|
||||
.then(({ data: teamsData, error: teamsError }) => {
|
||||
if (teamsError) {
|
||||
console.error('Fehler beim Abrufen der Teams:', teamsError);
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
if (teamMembers && teamMembers.length > 0) {
|
||||
// Extrahiere die Team-IDs
|
||||
const teamIds = teamMembers.map((member) => member.team_id);
|
||||
|
||||
if (teamsData && teamsData.length > 0) {
|
||||
// Hole die Organisationsnamen für jedes Team
|
||||
const orgIds = [...new Set(teamsData.map(team => team.organization_id))];
|
||||
|
||||
supabase
|
||||
.from('organizations')
|
||||
.select('id, name')
|
||||
.in('id', orgIds)
|
||||
.then(({ data: orgsData, error: orgsError }) => {
|
||||
if (orgsError) {
|
||||
console.error('Fehler beim Abrufen der Organisationen:', orgsError);
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
// Hole die Team-Details
|
||||
supabase
|
||||
.from('teams')
|
||||
.select('id, name, organization_id, created_at')
|
||||
.in('id', teamIds)
|
||||
.then(({ data: teamsData, error: teamsError }) => {
|
||||
if (teamsError) {
|
||||
console.error('Fehler beim Abrufen der Teams:', teamsError);
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Globales Event auslösen, um alle TeamList-Komponenten zu aktualisieren
|
||||
const event = new CustomEvent('teamlist-refresh', {
|
||||
detail: {
|
||||
teams: teamsData.map(team => {
|
||||
const org = orgsData?.find(org => org.id === team.organization_id);
|
||||
const memberInfo = teamMembers.find(member => member.team_id === team.id);
|
||||
|
||||
return {
|
||||
...team,
|
||||
organization_name: org?.name || 'Unbekannte Organisation',
|
||||
allocated_credits: memberInfo?.allocated_credits || 0,
|
||||
used_credits: memberInfo?.used_credits || 0
|
||||
};
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// Event auslösen
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
if (callback) callback();
|
||||
});
|
||||
} else {
|
||||
// Globales Event mit leerer Liste auslösen
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent('teamlist-refresh', { detail: { teams: [] } }));
|
||||
}
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Globales Event mit leerer Liste auslösen
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent('teamlist-refresh', { detail: { teams: [] } }));
|
||||
}
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
if (teamsData && teamsData.length > 0) {
|
||||
// Hole die Organisationsnamen für jedes Team
|
||||
const orgIds = [...new Set(teamsData.map((team) => team.organization_id))];
|
||||
|
||||
supabase
|
||||
.from('organizations')
|
||||
.select('id, name')
|
||||
.in('id', orgIds)
|
||||
.then(({ data: orgsData, error: orgsError }) => {
|
||||
if (orgsError) {
|
||||
console.error('Fehler beim Abrufen der Organisationen:', orgsError);
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Globales Event auslösen, um alle TeamList-Komponenten zu aktualisieren
|
||||
const event = new CustomEvent('teamlist-refresh', {
|
||||
detail: {
|
||||
teams: teamsData.map((team) => {
|
||||
const org = orgsData?.find((org) => org.id === team.organization_id);
|
||||
const memberInfo = teamMembers.find((member) => member.team_id === team.id);
|
||||
|
||||
return {
|
||||
...team,
|
||||
organization_name: org?.name || 'Unbekannte Organisation',
|
||||
allocated_credits: memberInfo?.allocated_credits || 0,
|
||||
used_credits: memberInfo?.used_credits || 0,
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
// Event auslösen
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
if (callback) callback();
|
||||
});
|
||||
} else {
|
||||
// Globales Event mit leerer Liste auslösen
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('teamlist-refresh', { detail: { teams: [] } })
|
||||
);
|
||||
}
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Globales Event mit leerer Liste auslösen
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent('teamlist-refresh', { detail: { teams: [] } }));
|
||||
}
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default function TeamList({ hideTitle = false }: TeamListProps) {
|
||||
const router = useRouter();
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [teams, setTeams] = useState<Team[]>([]);
|
||||
const { isDarkMode } = useTheme();
|
||||
const router = useRouter();
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [teams, setTeams] = useState<Team[]>([]);
|
||||
const { isDarkMode } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
// Prüfe den aktuellen Authentifizierungsstatus
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserTeams(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
// Prüfe den aktuellen Authentifizierungsstatus
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserTeams(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Abonniere Authentifizierungsänderungen
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserTeams(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Listener für teamlist-refresh Event hinzufügen
|
||||
const handleTeamListRefresh = (event: any) => {
|
||||
if (event.detail && event.detail.teams) {
|
||||
setTeams(event.detail.teams);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('teamlist-refresh', handleTeamListRefresh);
|
||||
}
|
||||
// Abonniere Authentifizierungsänderungen
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
if (session) {
|
||||
fetchUserTeams(session.user.id);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
if (typeof window !== 'undefined') {
|
||||
window.removeEventListener('teamlist-refresh', handleTeamListRefresh);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
// Listener für teamlist-refresh Event hinzufügen
|
||||
const handleTeamListRefresh = (event: any) => {
|
||||
if (event.detail && event.detail.teams) {
|
||||
setTeams(event.detail.teams);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
async function fetchUserTeams(userId: string) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Hole alle Teams, in denen der Benutzer Mitglied ist
|
||||
const { data: teamMembers, error: teamMembersError } = await supabase
|
||||
.from('team_members')
|
||||
.select('team_id, allocated_credits, used_credits')
|
||||
.eq('user_id', userId);
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('teamlist-refresh', handleTeamListRefresh);
|
||||
}
|
||||
|
||||
if (teamMembersError) throw teamMembersError;
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
if (typeof window !== 'undefined') {
|
||||
window.removeEventListener('teamlist-refresh', handleTeamListRefresh);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (teamMembers && teamMembers.length > 0) {
|
||||
// Extrahiere die Team-IDs
|
||||
const teamIds = teamMembers.map(member => member.team_id);
|
||||
async function fetchUserTeams(userId: string) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Hole die Team-Details
|
||||
const { data: teamsData, error: teamsError } = await supabase
|
||||
.from('teams')
|
||||
.select('id, name, organization_id, created_at')
|
||||
.in('id', teamIds);
|
||||
// Hole alle Teams, in denen der Benutzer Mitglied ist
|
||||
const { data: teamMembers, error: teamMembersError } = await supabase
|
||||
.from('team_members')
|
||||
.select('team_id, allocated_credits, used_credits')
|
||||
.eq('user_id', userId);
|
||||
|
||||
if (teamsError) throw teamsError;
|
||||
if (teamMembersError) throw teamMembersError;
|
||||
|
||||
if (teamsData && teamsData.length > 0) {
|
||||
// Hole die Organisationsnamen für jedes Team
|
||||
const orgIds = [...new Set(teamsData.map(team => team.organization_id))];
|
||||
|
||||
const { data: orgsData, error: orgsError } = await supabase
|
||||
.from('organizations')
|
||||
.select('id, name')
|
||||
.in('id', orgIds);
|
||||
if (teamMembers && teamMembers.length > 0) {
|
||||
// Extrahiere die Team-IDs
|
||||
const teamIds = teamMembers.map((member) => member.team_id);
|
||||
|
||||
if (orgsError) throw orgsError;
|
||||
// Hole die Team-Details
|
||||
const { data: teamsData, error: teamsError } = await supabase
|
||||
.from('teams')
|
||||
.select('id, name, organization_id, created_at')
|
||||
.in('id', teamIds);
|
||||
|
||||
// Füge Organisationsnamen und Kredit-Informationen zu den Teams hinzu
|
||||
const enhancedTeams = teamsData.map(team => {
|
||||
const org = orgsData?.find(org => org.id === team.organization_id);
|
||||
const memberInfo = teamMembers.find(member => member.team_id === team.id);
|
||||
|
||||
return {
|
||||
...team,
|
||||
organization_name: org?.name || 'Unbekannte Organisation',
|
||||
allocated_credits: memberInfo?.allocated_credits || 0,
|
||||
used_credits: memberInfo?.used_credits || 0
|
||||
};
|
||||
});
|
||||
if (teamsError) throw teamsError;
|
||||
|
||||
setTeams(enhancedTeams);
|
||||
} else {
|
||||
setTeams([]);
|
||||
}
|
||||
} else {
|
||||
setTeams([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Teams:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Laden Ihrer Teams aufgetreten.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
if (teamsData && teamsData.length > 0) {
|
||||
// Hole die Organisationsnamen für jedes Team
|
||||
const orgIds = [...new Set(teamsData.map((team) => team.organization_id))];
|
||||
|
||||
// Keine Aufklappfunktion mehr benötigt
|
||||
const { data: orgsData, error: orgsError } = await supabase
|
||||
.from('organizations')
|
||||
.select('id, name')
|
||||
.in('id', orgIds);
|
||||
|
||||
const navigateToTeamDetails = (teamId: string, teamName: string) => {
|
||||
// Verwende die korrekte Expo Router Navigation
|
||||
router.push({
|
||||
pathname: '/teams/[id]',
|
||||
params: { id: teamId, name: teamName }
|
||||
});
|
||||
};
|
||||
if (orgsError) throw orgsError;
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
// Füge Organisationsnamen und Kredit-Informationen zu den Teams hinzu
|
||||
const enhancedTeams = teamsData.map((team) => {
|
||||
const org = orgsData?.find((org) => org.id === team.organization_id);
|
||||
const memberInfo = teamMembers.find((member) => member.team_id === team.id);
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center p-5 mx-2.5`}>
|
||||
Bitte melden Sie sich an, um Ihre Teams zu sehen.
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
return {
|
||||
...team,
|
||||
organization_name: org?.name || 'Unbekannte Organisation',
|
||||
allocated_credits: memberInfo?.allocated_credits || 0,
|
||||
used_credits: memberInfo?.used_credits || 0,
|
||||
};
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View className="flex-1 items-center justify-center p-4 mx-2.5">
|
||||
<ActivityIndicator size="large" color="#0055FF" />
|
||||
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center mt-2.5`}>Lade Teams...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
setTeams(enhancedTeams);
|
||||
} else {
|
||||
setTeams([]);
|
||||
}
|
||||
} else {
|
||||
setTeams([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Teams:', error);
|
||||
Alert.alert('Fehler', 'Es ist ein Fehler beim Laden Ihrer Teams aufgetreten.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (teams.length === 0) {
|
||||
return (
|
||||
<View className="flex-1 items-center justify-center p-4 mx-2.5">
|
||||
<FontAwesome5 name="users-slash" size={50} color={isDarkMode ? '#4B5563' : '#ccc'} className="mb-4" />
|
||||
<Text className={`text-lg font-medium ${isDarkMode ? 'text-gray-100' : 'text-gray-800'} text-center mb-2.5`}>
|
||||
Sie sind derzeit kein Mitglied in einem Team.
|
||||
</Text>
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center px-5`}>
|
||||
Erstellen Sie ein neues Team oder bitten Sie einen Administrator, Sie einem Team hinzuzufügen.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Keine Aufklappfunktion mehr benötigt
|
||||
|
||||
return (
|
||||
<>
|
||||
{!hideTitle && (
|
||||
<Text className={`text-2xl font-bold mb-4 ${isDarkMode ? 'text-gray-100' : 'text-gray-800'} px-2.5`}>Meine Teams</Text>
|
||||
)}
|
||||
<FlatList
|
||||
data={teams}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
className={`${isDarkMode ? 'bg-gray-700' : 'bg-gray-50'} rounded-lg p-4 mb-2.5 shadow-sm`}
|
||||
style={Platform.OS === 'web' ? { cursor: 'pointer' } : undefined}
|
||||
onPress={() => navigateToTeamDetails(item.id, item.name)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{/* Header mit Teamname */}
|
||||
<View className="flex-row justify-between items-center mb-3">
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5 name="users" size={18} color={isDarkMode ? '#93C5FD' : '#0055FF'} className="mr-2.5" />
|
||||
<Text className={`text-lg font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>{item.name}</Text>
|
||||
</View>
|
||||
<FontAwesome5 name="chevron-right" size={14} color={isDarkMode ? '#93C5FD' : '#0055FF'} />
|
||||
</View>
|
||||
|
||||
{/* Mittlerer Bereich mit Organisation und Erstellungsdatum */}
|
||||
<View className="flex-row justify-between items-center mb-3">
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5 name="building" size={14} color={isDarkMode ? '#9CA3AF' : '#666'} className="mr-2" />
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}>{item.organization_name}</Text>
|
||||
</View>
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5 name="calendar-alt" size={14} color={isDarkMode ? '#9CA3AF' : '#666'} className="mr-2" />
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}>{formatDate(item.created_at)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Unterer Bereich mit Kreditinformationen */}
|
||||
<View className={`flex-row justify-between mt-1 pt-2 border-t ${isDarkMode ? 'border-gray-600' : 'border-gray-200'}`}>
|
||||
<View className="flex-1 items-center">
|
||||
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}>Zugewiesen</Text>
|
||||
<Text className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>{item.allocated_credits}</Text>
|
||||
</View>
|
||||
|
||||
<View className={`flex-1 items-center border-x ${isDarkMode ? 'border-gray-600' : 'border-gray-200'}`}>
|
||||
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}>Verwendet</Text>
|
||||
<Text className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>{item.used_credits}</Text>
|
||||
</View>
|
||||
|
||||
<View className="flex-1 items-center">
|
||||
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}>Verfügbar</Text>
|
||||
<Text
|
||||
className={`text-base font-semibold ${(item.allocated_credits || 0) - (item.used_credits || 0) > 0 ? (isDarkMode ? 'text-green-400' : 'text-green-600') : (isDarkMode ? 'text-red-400' : 'text-red-600')}`}
|
||||
>
|
||||
{(item.allocated_credits || 0) - (item.used_credits || 0)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
className="pb-5 px-2.5"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
const navigateToTeamDetails = (teamId: string, teamName: string) => {
|
||||
// Verwende die korrekte Expo Router Navigation
|
||||
router.push({
|
||||
pathname: '/teams/[id]',
|
||||
params: { id: teamId, name: teamName },
|
||||
});
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
};
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<Text
|
||||
className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} mx-2.5 p-5 text-center`}
|
||||
>
|
||||
Bitte melden Sie sich an, um Ihre Teams zu sehen.
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View className="mx-2.5 flex-1 items-center justify-center p-4">
|
||||
<ActivityIndicator size="large" color="#0055FF" />
|
||||
<Text
|
||||
className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} mt-2.5 text-center`}
|
||||
>
|
||||
Lade Teams...
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (teams.length === 0) {
|
||||
return (
|
||||
<View className="mx-2.5 flex-1 items-center justify-center p-4">
|
||||
<FontAwesome5
|
||||
name="users-slash"
|
||||
size={50}
|
||||
color={isDarkMode ? '#4B5563' : '#ccc'}
|
||||
className="mb-4"
|
||||
/>
|
||||
<Text
|
||||
className={`text-lg font-medium ${isDarkMode ? 'text-gray-100' : 'text-gray-800'} mb-2.5 text-center`}
|
||||
>
|
||||
Sie sind derzeit kein Mitglied in einem Team.
|
||||
</Text>
|
||||
<Text
|
||||
className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} px-5 text-center`}
|
||||
>
|
||||
Erstellen Sie ein neues Team oder bitten Sie einen Administrator, Sie einem Team
|
||||
hinzuzufügen.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!hideTitle && (
|
||||
<Text
|
||||
className={`mb-4 text-2xl font-bold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'} px-2.5`}
|
||||
>
|
||||
Meine Teams
|
||||
</Text>
|
||||
)}
|
||||
<FlatList
|
||||
data={teams}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
className={`${isDarkMode ? 'bg-gray-700' : 'bg-gray-50'} mb-2.5 rounded-lg p-4 shadow-sm`}
|
||||
style={Platform.OS === 'web' ? { cursor: 'pointer' } : undefined}
|
||||
onPress={() => navigateToTeamDetails(item.id, item.name)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{/* Header mit Teamname */}
|
||||
<View className="mb-3 flex-row items-center justify-between">
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5
|
||||
name="users"
|
||||
size={18}
|
||||
color={isDarkMode ? '#93C5FD' : '#0055FF'}
|
||||
className="mr-2.5"
|
||||
/>
|
||||
<Text
|
||||
className={`text-lg font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</View>
|
||||
<FontAwesome5
|
||||
name="chevron-right"
|
||||
size={14}
|
||||
color={isDarkMode ? '#93C5FD' : '#0055FF'}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Mittlerer Bereich mit Organisation und Erstellungsdatum */}
|
||||
<View className="mb-3 flex-row items-center justify-between">
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5
|
||||
name="building"
|
||||
size={14}
|
||||
color={isDarkMode ? '#9CA3AF' : '#666'}
|
||||
className="mr-2"
|
||||
/>
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}>
|
||||
{item.organization_name}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="flex-row items-center">
|
||||
<FontAwesome5
|
||||
name="calendar-alt"
|
||||
size={14}
|
||||
color={isDarkMode ? '#9CA3AF' : '#666'}
|
||||
className="mr-2"
|
||||
/>
|
||||
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}`}>
|
||||
{formatDate(item.created_at)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Unterer Bereich mit Kreditinformationen */}
|
||||
<View
|
||||
className={`mt-1 flex-row justify-between border-t pt-2 ${isDarkMode ? 'border-gray-600' : 'border-gray-200'}`}
|
||||
>
|
||||
<View className="flex-1 items-center">
|
||||
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}>
|
||||
Zugewiesen
|
||||
</Text>
|
||||
<Text
|
||||
className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
{item.allocated_credits}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
className={`flex-1 items-center border-x ${isDarkMode ? 'border-gray-600' : 'border-gray-200'}`}
|
||||
>
|
||||
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}>
|
||||
Verwendet
|
||||
</Text>
|
||||
<Text
|
||||
className={`text-base font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}
|
||||
>
|
||||
{item.used_credits}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className="flex-1 items-center">
|
||||
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-600'} mb-1`}>
|
||||
Verfügbar
|
||||
</Text>
|
||||
<Text
|
||||
className={`text-base font-semibold ${(item.allocated_credits || 0) - (item.used_credits || 0) > 0 ? (isDarkMode ? 'text-green-400' : 'text-green-600') : isDarkMode ? 'text-red-400' : 'text-red-600'}`}
|
||||
>
|
||||
{(item.allocated_credits || 0) - (item.used_credits || 0)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
className="px-2.5 pb-5"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// NativeWind wird für das Styling verwendet, daher sind keine StyleSheet-Definitionen erforderlich
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue