mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-16 07:19:40 +02:00
Projects included: - maerchenzauber (NestJS backend + Expo mobile + SvelteKit web + Astro landing) - manacore (Expo mobile + SvelteKit web + Astro landing) - manadeck (NestJS backend + Expo mobile + SvelteKit web) - memoro (Expo mobile + SvelteKit web + Astro landing) This commit preserves the current state before monorepo restructuring. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
664 lines
24 KiB
TypeScript
664 lines
24 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { View, Text, FlatList, TouchableOpacity, ActivityIndicator, Alert, TextInput } from 'react-native';
|
|
import { FontAwesome5 } from '@expo/vector-icons';
|
|
import { supabase } from '../utils/supabase';
|
|
import { Session } from '@supabase/supabase-js';
|
|
import { useLocalSearchParams } from 'expo-router';
|
|
import { useTheme } from '../utils/themeContext';
|
|
|
|
interface TeamMember {
|
|
// Anpassung an die tatsächliche Datenbankstruktur
|
|
user_id: string;
|
|
team_id: string;
|
|
allocated_credits: number;
|
|
used_credits: number;
|
|
first_name?: string;
|
|
last_name?: string;
|
|
created_at: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
interface TeamDetails {
|
|
id: string;
|
|
name: string;
|
|
organization_id: string;
|
|
organization_name?: string;
|
|
created_at: string;
|
|
}
|
|
|
|
interface TeamMembersProps {
|
|
teamId: string;
|
|
}
|
|
|
|
export default function TeamMembers({ teamId }: TeamMembersProps) {
|
|
const [session, setSession] = useState<Session | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [teamDetails, setTeamDetails] = useState<TeamDetails | null>(null);
|
|
const [members, setMembers] = useState<TeamMember[]>([]);
|
|
const [newMemberEmail, setNewMemberEmail] = useState('');
|
|
const [inviting, setInviting] = useState(false);
|
|
const [userRole, setUserRole] = useState<string | null>(null);
|
|
const [isAdmin, setIsAdmin] = useState(false);
|
|
const { isDarkMode } = useTheme();
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [newTeamName, setNewTeamName] = useState('');
|
|
const [updatingName, setUpdatingName] = useState(false);
|
|
|
|
// Zustandsvariablen für die Bearbeitung der Mitgliederlimits
|
|
const [editingMemberId, setEditingMemberId] = useState<string | null>(null);
|
|
const [newCreditLimit, setNewCreditLimit] = useState('');
|
|
const [updatingLimit, setUpdatingLimit] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!teamId) {
|
|
Alert.alert('Fehler', 'Team-ID nicht gefunden');
|
|
return;
|
|
}
|
|
|
|
// Setze den neuen Teamnamen, wenn sich die Teamdetails ändern
|
|
if (teamDetails) {
|
|
setNewTeamName(teamDetails.name);
|
|
}
|
|
|
|
// Prüfe den aktuellen Authentifizierungsstatus
|
|
supabase.auth.getSession().then(({ data: { session } }) => {
|
|
setSession(session);
|
|
if (session) {
|
|
fetchTeamDetails(teamId);
|
|
fetchTeamMembers(teamId);
|
|
checkUserRole(session.user.id, teamId);
|
|
} else {
|
|
setLoading(false);
|
|
}
|
|
});
|
|
|
|
// Abonniere Authentifizierungsänderungen
|
|
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
|
setSession(session);
|
|
if (session) {
|
|
fetchTeamDetails(teamId);
|
|
fetchTeamMembers(teamId);
|
|
checkUserRole(session.user.id, teamId);
|
|
} else {
|
|
setLoading(false);
|
|
}
|
|
});
|
|
|
|
return () => subscription.unsubscribe();
|
|
}, [teamId]);
|
|
|
|
async function checkUserRole(userId: string, teamId: string) {
|
|
try {
|
|
// Vereinfachte Version: Wir setzen isAdmin auf true, damit die Optionen zum Hinzufügen und Entfernen
|
|
// von Teammitgliedern immer angezeigt werden
|
|
setIsAdmin(true);
|
|
setUserRole('admin');
|
|
|
|
// Prüfe, ob der Benutzer ein Teammitglied ist
|
|
const { data: memberData, error: memberError } = await supabase
|
|
.from('team_members')
|
|
.select('user_id, team_id')
|
|
.eq('user_id', userId)
|
|
.eq('team_id', teamId);
|
|
|
|
if (memberError) {
|
|
console.error('Fehler beim Prüfen der Mitgliedschaft:', memberError);
|
|
}
|
|
|
|
// Wenn der Benutzer kein Mitglied ist, setzen wir die Rolle auf null
|
|
if (!memberData || memberData.length === 0) {
|
|
setUserRole(null);
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Prüfen der Benutzerrolle:', error);
|
|
}
|
|
}
|
|
|
|
async function fetchTeamDetails(teamId: string) {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('teams')
|
|
.select('id, name, organization_id, created_at')
|
|
.eq('id', teamId)
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
|
|
if (data) {
|
|
// Hole den Organisationsnamen
|
|
const { data: orgData, error: orgError } = await supabase
|
|
.from('organizations')
|
|
.select('name')
|
|
.eq('id', data.organization_id)
|
|
.single();
|
|
|
|
if (orgError) throw orgError;
|
|
|
|
setTeamDetails({
|
|
...data,
|
|
organization_name: orgData?.name || 'Unbekannte Organisation'
|
|
});
|
|
|
|
// Setze den neuen Teamnamen
|
|
setNewTeamName(data.name);
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Abrufen der Team-Details:', error);
|
|
Alert.alert('Fehler', 'Es ist ein Fehler beim Laden der Team-Details aufgetreten.');
|
|
}
|
|
}
|
|
|
|
const startEditing = () => {
|
|
setIsEditing(true);
|
|
if (teamDetails) {
|
|
setNewTeamName(teamDetails.name);
|
|
}
|
|
};
|
|
|
|
const cancelEditing = () => {
|
|
setIsEditing(false);
|
|
if (teamDetails) {
|
|
setNewTeamName(teamDetails.name);
|
|
}
|
|
};
|
|
|
|
const updateTeamName = async () => {
|
|
if (!newTeamName.trim()) {
|
|
Alert.alert('Fehler', 'Der Teamname darf nicht leer sein.');
|
|
return;
|
|
}
|
|
|
|
if (teamDetails && newTeamName.trim() === teamDetails.name) {
|
|
setIsEditing(false);
|
|
return;
|
|
}
|
|
|
|
setUpdatingName(true);
|
|
|
|
try {
|
|
const { error } = await supabase
|
|
.from('teams')
|
|
.update({ name: newTeamName.trim() })
|
|
.eq('id', teamId);
|
|
|
|
if (error) throw error;
|
|
|
|
// Aktualisiere die Teamdetails
|
|
if (teamDetails) {
|
|
setTeamDetails({
|
|
...teamDetails,
|
|
name: newTeamName.trim()
|
|
});
|
|
}
|
|
|
|
setIsEditing(false);
|
|
Alert.alert('Erfolg', 'Der Teamname wurde erfolgreich aktualisiert.');
|
|
} catch (error) {
|
|
console.error('Fehler beim Aktualisieren des Teamnamens:', error);
|
|
Alert.alert('Fehler', 'Es ist ein Fehler beim Aktualisieren des Teamnamens aufgetreten.');
|
|
} finally {
|
|
setUpdatingName(false);
|
|
}
|
|
};
|
|
|
|
async function fetchTeamMembers(teamId: string) {
|
|
try {
|
|
setLoading(true);
|
|
|
|
// Hole alle Teammitglieder basierend auf der tatsächlichen Datenbankstruktur
|
|
const { data: memberData, error: memberError } = await supabase
|
|
.from('team_members')
|
|
.select('user_id, team_id, allocated_credits, used_credits, created_at, updated_at')
|
|
.eq('team_id', teamId);
|
|
|
|
if (memberError) throw memberError;
|
|
|
|
if (memberData && memberData.length > 0) {
|
|
// Hole die Benutzerprofile für jedes Mitglied
|
|
const userIds = memberData.map(member => member.user_id);
|
|
|
|
// Abfrage der Profilinformationen aus der profiles-Tabelle
|
|
const { data: userData, error: userError } = await supabase
|
|
.from('profiles')
|
|
.select('id, first_name, last_name')
|
|
.in('id', userIds);
|
|
|
|
if (userError) throw userError;
|
|
|
|
// Füge Benutzerinformationen zu den Mitgliedern hinzu
|
|
const enhancedMembers = memberData.map(member => {
|
|
const user = userData?.find(u => u.id === member.user_id);
|
|
return {
|
|
...member,
|
|
first_name: user?.first_name,
|
|
last_name: user?.last_name
|
|
};
|
|
});
|
|
|
|
setMembers(enhancedMembers as TeamMember[]);
|
|
} else {
|
|
setMembers([]);
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Abrufen der Teammitglieder:', error);
|
|
Alert.alert('Fehler', 'Es ist ein Fehler beim Laden der Teammitglieder aufgetreten.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
async function inviteMember() {
|
|
if (!newMemberEmail || !newMemberEmail.includes('@')) {
|
|
Alert.alert('Fehler', 'Bitte geben Sie eine gültige E-Mail-Adresse ein.');
|
|
return;
|
|
}
|
|
|
|
if (!teamId || !isAdmin) {
|
|
Alert.alert('Fehler', 'Sie haben keine Berechtigung, Mitglieder einzuladen.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setInviting(true);
|
|
|
|
// Suche nach dem Benutzer mit dieser E-Mail
|
|
const { data: profiles, error: profilesError } = await supabase
|
|
.from('profiles')
|
|
.select('id, email')
|
|
.ilike('email', newMemberEmail.trim().toLowerCase());
|
|
|
|
if (profilesError) {
|
|
console.error('Fehler beim Suchen des Benutzers:', profilesError);
|
|
Alert.alert('Fehler', 'Benutzer konnte nicht gefunden werden.');
|
|
return;
|
|
}
|
|
|
|
if (!profiles || profiles.length === 0) {
|
|
// Wenn der Benutzer nicht gefunden wurde, zeigen wir eine Meldung an
|
|
Alert.alert(
|
|
'Benutzer nicht gefunden',
|
|
'Möchten Sie eine Einladung an diese E-Mail-Adresse senden?',
|
|
[
|
|
{
|
|
text: 'Abbrechen',
|
|
style: 'cancel'
|
|
},
|
|
{
|
|
text: 'Einladen',
|
|
onPress: () => {
|
|
// Hier könnte eine Einladungs-E-Mail gesendet werden
|
|
// Für jetzt zeigen wir nur eine Erfolgsmeldung an
|
|
Alert.alert('Erfolg', `Eine Einladung wurde an ${newMemberEmail} gesendet.`);
|
|
setNewMemberEmail('');
|
|
}
|
|
}
|
|
]
|
|
);
|
|
return;
|
|
}
|
|
|
|
const userId = profiles[0].id;
|
|
|
|
// Prüfe, ob der Benutzer bereits Mitglied ist
|
|
const { data: existingMember, error: existingError } = await supabase
|
|
.from('team_members')
|
|
.select('user_id, team_id')
|
|
.eq('user_id', userId)
|
|
.eq('team_id', teamId);
|
|
|
|
if (existingError) throw existingError;
|
|
|
|
if (existingMember && existingMember.length > 0) {
|
|
Alert.alert('Information', 'Dieser Benutzer ist bereits Mitglied des Teams.');
|
|
return;
|
|
}
|
|
|
|
// Füge den Benutzer zum Team hinzu
|
|
const { error: addError } = await supabase
|
|
.from('team_members')
|
|
.insert([
|
|
{
|
|
user_id: userId,
|
|
team_id: teamId,
|
|
allocated_credits: 0,
|
|
used_credits: 0
|
|
}
|
|
]);
|
|
|
|
if (addError) throw addError;
|
|
|
|
Alert.alert('Erfolg', 'Benutzer wurde erfolgreich zum Team hinzugefügt.');
|
|
setNewMemberEmail('');
|
|
fetchTeamMembers(teamId);
|
|
} catch (error) {
|
|
console.error('Fehler beim Einladen des Benutzers:', error);
|
|
Alert.alert('Fehler', 'Es ist ein Fehler beim Einladen des Benutzers aufgetreten.');
|
|
} finally {
|
|
setInviting(false);
|
|
}
|
|
}
|
|
|
|
// Direktes Entfernen ohne Alert-Dialog
|
|
async function removeMember(userId: string, memberName: string) {
|
|
if (!isAdmin) {
|
|
Alert.alert('Fehler', 'Sie haben keine Berechtigung, Mitglieder zu entfernen.');
|
|
return;
|
|
}
|
|
|
|
console.log(`Versuche, Mitglied zu entfernen: ${memberName} (${userId}) aus Team ${teamId}`);
|
|
|
|
try {
|
|
console.log('Sende Löschanfrage an Supabase...');
|
|
console.log(`Parameter: user_id=${userId}, team_id=${teamId}`);
|
|
|
|
// Beachte, dass team_members einen zusammengesetzten Primärschlüssel aus team_id und user_id hat
|
|
const { data, error } = await supabase
|
|
.from('team_members')
|
|
.delete()
|
|
.eq('user_id', userId)
|
|
.eq('team_id', teamId)
|
|
.select();
|
|
|
|
if (error) {
|
|
console.error('Supabase-Fehler beim Entfernen:', error);
|
|
Alert.alert('Fehler', `Fehler beim Entfernen des Mitglieds: ${error.message}`);
|
|
return;
|
|
}
|
|
|
|
console.log('Löschantwort von Supabase:', data);
|
|
Alert.alert('Erfolg', 'Mitglied wurde erfolgreich aus dem Team entfernt.');
|
|
|
|
// Aktualisiere die Mitgliederliste
|
|
fetchTeamMembers(teamId);
|
|
} catch (error) {
|
|
console.error('Fehler beim Entfernen des Mitglieds:', error);
|
|
Alert.alert('Fehler', 'Es ist ein Fehler beim Entfernen des Mitglieds aufgetreten. Bitte überprüfen Sie die Konsole für weitere Details.');
|
|
}
|
|
}
|
|
|
|
// Funktion zum Starten der Bearbeitung des Credit-Limits für ein Mitglied
|
|
const startEditingLimit = (userId: string, currentLimit: number) => {
|
|
if (!isAdmin) {
|
|
Alert.alert('Fehler', 'Sie haben keine Berechtigung, Mitgliederlimits zu bearbeiten.');
|
|
return;
|
|
}
|
|
|
|
setEditingMemberId(userId);
|
|
setNewCreditLimit(currentLimit.toString());
|
|
};
|
|
|
|
// Funktion zum Abbrechen der Bearbeitung
|
|
const cancelEditingLimit = () => {
|
|
setEditingMemberId(null);
|
|
setNewCreditLimit('');
|
|
};
|
|
|
|
// Funktion zum Aktualisieren des Credit-Limits eines Mitglieds
|
|
const updateMemberLimit = async () => {
|
|
if (!editingMemberId || !teamId) {
|
|
Alert.alert('Fehler', 'Mitglied oder Team-ID nicht gefunden.');
|
|
return;
|
|
}
|
|
|
|
const creditLimit = parseInt(newCreditLimit);
|
|
if (isNaN(creditLimit) || creditLimit < 0) {
|
|
Alert.alert('Fehler', 'Bitte geben Sie einen gültigen Wert für das Credit-Limit ein.');
|
|
return;
|
|
}
|
|
|
|
setUpdatingLimit(true);
|
|
|
|
try {
|
|
// Aktualisiere das Credit-Limit in der Datenbank
|
|
const { error } = await supabase
|
|
.from('team_members')
|
|
.update({ allocated_credits: creditLimit })
|
|
.eq('user_id', editingMemberId)
|
|
.eq('team_id', teamId);
|
|
|
|
if (error) throw error;
|
|
|
|
// Aktualisiere die lokale Mitgliederliste
|
|
setMembers(members.map(member => {
|
|
if (member.user_id === editingMemberId) {
|
|
return { ...member, allocated_credits: creditLimit };
|
|
}
|
|
return member;
|
|
}));
|
|
|
|
Alert.alert('Erfolg', 'Das Credit-Limit wurde erfolgreich aktualisiert.');
|
|
setEditingMemberId(null);
|
|
} catch (error) {
|
|
console.error('Fehler beim Aktualisieren des Credit-Limits:', error);
|
|
Alert.alert('Fehler', 'Es ist ein Fehler beim Aktualisieren des Credit-Limits aufgetreten.');
|
|
} finally {
|
|
setUpdatingLimit(false);
|
|
}
|
|
};
|
|
|
|
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 Teammitglieder zu sehen.
|
|
</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
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="#0055FF" />
|
|
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center mt-2.5`}>Lade Teammitglieder...</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View className="flex-1">
|
|
{teamDetails && (
|
|
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-4 m-2.5 shadow`}>
|
|
{isEditing ? (
|
|
<View>
|
|
<View className="flex-row items-center mb-3">
|
|
<Text className={`text-lg font-bold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'} mr-2`}>Teamname bearbeiten:</Text>
|
|
</View>
|
|
<View className="flex-row items-center">
|
|
<TextInput
|
|
className={`flex-1 ${isDarkMode ? 'bg-gray-700 border-gray-600 text-white' : 'bg-gray-50 border-gray-300 text-gray-800'} border rounded-lg px-3 py-2 mr-2`}
|
|
value={newTeamName}
|
|
onChangeText={setNewTeamName}
|
|
placeholder="Teamname"
|
|
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#6B7280'}
|
|
/>
|
|
</View>
|
|
<View className="flex-row justify-end mt-3 mb-2">
|
|
<TouchableOpacity
|
|
className={`flex-row items-center py-2 px-4 rounded-lg mr-2 ${isDarkMode ? 'bg-gray-700' : 'bg-gray-200'}`}
|
|
onPress={cancelEditing}
|
|
disabled={updatingName}
|
|
>
|
|
<Text className={`${isDarkMode ? 'text-gray-300' : 'text-gray-700'} font-semibold`}>Abbrechen</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
className={`flex-row items-center py-2 px-4 rounded-lg ${isDarkMode ? 'bg-blue-700' : 'bg-blue-600'}`}
|
|
onPress={updateTeamName}
|
|
disabled={updatingName}
|
|
>
|
|
{updatingName ? (
|
|
<Text className="text-white font-semibold">Speichern...</Text>
|
|
) : (
|
|
<Text className="text-white font-semibold">Speichern</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
</View>
|
|
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} mb-3`}>
|
|
Organisation: {teamDetails.organization_name}
|
|
</Text>
|
|
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
|
|
Erstellt am: {formatDate(teamDetails.created_at)}
|
|
</Text>
|
|
</View>
|
|
) : (
|
|
<View>
|
|
<View className="flex-row items-center justify-between mb-1">
|
|
<Text className={`text-xl font-bold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>{teamDetails.name}</Text>
|
|
{isAdmin && (
|
|
<TouchableOpacity
|
|
className={`p-2 rounded-full ${isDarkMode ? 'bg-gray-700' : 'bg-gray-100'}`}
|
|
onPress={startEditing}
|
|
>
|
|
<FontAwesome5 name="edit" size={16} color={isDarkMode ? '#93C5FD' : '#0055FF'} />
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
<Text className={`text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} mb-3`}>
|
|
Organisation: {teamDetails.organization_name}
|
|
</Text>
|
|
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
|
|
Erstellt am: {formatDate(teamDetails.created_at)}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
|
|
<View className={`${isDarkMode ? 'bg-gray-800' : 'bg-white'} rounded-lg p-4 m-2.5 shadow`}>
|
|
<Text className={`text-lg font-semibold ${isDarkMode ? 'text-gray-100' : 'text-gray-800'} mb-4`}>Teammitglieder</Text>
|
|
|
|
{isAdmin && (
|
|
<View className={`mb-4 ${isDarkMode ? 'bg-gray-700' : 'bg-gray-50'} p-3 rounded-lg`}>
|
|
<Text className={`text-sm font-medium ${isDarkMode ? 'text-gray-200' : 'text-gray-700'} mb-2`}>Neues Mitglied einladen</Text>
|
|
<View className="flex-row">
|
|
<TextInput
|
|
className={`flex-1 ${isDarkMode ? 'bg-gray-600 border-gray-500 text-white' : 'bg-white border-gray-300 text-gray-800'} border rounded-lg px-3 py-2 mr-2`}
|
|
placeholder="E-Mail-Adresse"
|
|
placeholderTextColor={isDarkMode ? '#9CA3AF' : '#6B7280'}
|
|
value={newMemberEmail}
|
|
onChangeText={setNewMemberEmail}
|
|
autoCapitalize="none"
|
|
keyboardType="email-address"
|
|
/>
|
|
<TouchableOpacity
|
|
className="bg-blue-600 rounded-lg px-4 py-2 items-center justify-center"
|
|
onPress={inviteMember}
|
|
disabled={inviting}
|
|
>
|
|
{inviting ? (
|
|
<ActivityIndicator size="small" color="white" />
|
|
) : (
|
|
<Text className="text-white font-medium">Einladen</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{members.length === 0 ? (
|
|
<View className="items-center justify-center py-8">
|
|
<FontAwesome5 name="users-slash" size={50} color={isDarkMode ? '#4B5563' : '#ccc'} />
|
|
<Text className={`text-base ${isDarkMode ? 'text-gray-300' : 'text-gray-600'} text-center mt-4`}>
|
|
Keine Mitglieder in diesem Team gefunden.
|
|
</Text>
|
|
</View>
|
|
) : (
|
|
<FlatList
|
|
data={members}
|
|
keyExtractor={(item) => item.user_id}
|
|
renderItem={({ item }) => (
|
|
<View className={`flex-row items-center justify-between ${isDarkMode ? 'bg-gray-700' : 'bg-gray-50'} p-3 rounded-lg mb-2`}>
|
|
<View className="flex-1">
|
|
<Text className={`text-base font-medium ${isDarkMode ? 'text-gray-100' : 'text-gray-800'}`}>
|
|
{item.first_name && item.last_name
|
|
? `${item.first_name} ${item.last_name}`
|
|
: `Benutzer ${item.user_id.substring(0, 8)}...`}
|
|
</Text>
|
|
<View className="flex-row items-center mt-1">
|
|
{editingMemberId === item.user_id ? (
|
|
<View className="flex-row items-center mr-4">
|
|
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'} mr-2`}>
|
|
Credits:
|
|
</Text>
|
|
<TextInput
|
|
className={`${isDarkMode ? 'bg-gray-600 border-gray-500 text-white' : 'bg-white border-gray-300 text-gray-800'} border rounded px-2 py-1 w-16 text-xs`}
|
|
value={newCreditLimit}
|
|
onChangeText={setNewCreditLimit}
|
|
keyboardType="numeric"
|
|
autoFocus
|
|
/>
|
|
<TouchableOpacity
|
|
onPress={updateMemberLimit}
|
|
className="ml-2 p-1"
|
|
disabled={updatingLimit}
|
|
>
|
|
<FontAwesome5 name="check" size={12} color={isDarkMode ? '#10B981' : '#059669'} />
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
onPress={cancelEditingLimit}
|
|
className="ml-1 p-1"
|
|
disabled={updatingLimit}
|
|
>
|
|
<FontAwesome5 name="times" size={12} color={isDarkMode ? '#F87171' : '#EF4444'} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
) : (
|
|
<TouchableOpacity
|
|
onPress={() => isAdmin && startEditingLimit(item.user_id, item.allocated_credits)}
|
|
className={`flex-row items-center mr-4 ${isAdmin ? 'opacity-100' : 'opacity-80'}`}
|
|
>
|
|
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
|
|
Zugewiesene Credits: {item.allocated_credits}
|
|
</Text>
|
|
{isAdmin && (
|
|
<FontAwesome5
|
|
name="edit"
|
|
size={10}
|
|
color={isDarkMode ? '#93C5FD' : '#0055FF'}
|
|
style={{ marginLeft: 4 }}
|
|
/>
|
|
)}
|
|
</TouchableOpacity>
|
|
)}
|
|
<Text className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
|
|
Genutzte Credits: {item.used_credits}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
{isAdmin && (
|
|
<View className="flex-row">
|
|
<TouchableOpacity
|
|
className={`${isDarkMode ? 'bg-blue-900' : 'bg-blue-100'} p-2 rounded-full mr-2`}
|
|
onPress={() => startEditingLimit(item.user_id, item.allocated_credits)}
|
|
>
|
|
<FontAwesome5 name="sliders-h" size={16} color={isDarkMode ? '#93C5FD' : '#0055FF'} />
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
className={`${isDarkMode ? 'bg-red-900' : 'bg-red-100'} p-2 rounded-full`}
|
|
onPress={() => removeMember(item.user_id, item.first_name && item.last_name
|
|
? `${item.first_name} ${item.last_name}`
|
|
: `Benutzer ${item.user_id.substring(0, 8)}...`)}
|
|
>
|
|
<FontAwesome5 name="user-minus" size={16} color={isDarkMode ? '#F87171' : '#EF4444'} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
/>
|
|
)}
|
|
</View>
|
|
|
|
|
|
</View>
|
|
);
|
|
}
|