botton navi...
This commit is contained in:
@@ -9,11 +9,17 @@ import {
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
Share,
|
||||
} from 'react-native';
|
||||
import { RootStackParamList } from '../../navigation/AppNavigator'; // Adjust path
|
||||
import { apiCall } from '../../utils/ApiHelper'; // Adjust path
|
||||
import AppView from '../../components/AppView';
|
||||
import { Picker } from '@react-native-picker/picker';
|
||||
import colors from '../../components/colors';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { CompositeScreenProps } from '@react-navigation/native';
|
||||
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
|
||||
import { ManagementTabParamList } from './ManagementTabNavigation'; // Adjust path
|
||||
|
||||
// --- Type Definitions ---
|
||||
interface Participant {
|
||||
@@ -35,6 +41,8 @@ export interface GroupDetails {
|
||||
maxParticipants: number;
|
||||
portalCharge: number | null;
|
||||
latePaymentCharge: number | null;
|
||||
lateFeeMode: 'DAILY' | 'FIXED' | null;
|
||||
lateFeeValue: number | null;
|
||||
cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM';
|
||||
customCycleDays: number;
|
||||
withdrawalMethod: 'RANDOM' | 'SEQUENTIAL';
|
||||
@@ -54,16 +62,23 @@ export interface EditGroupPayload {
|
||||
cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM';
|
||||
latePaymentCharge: number;
|
||||
visibility: 'PUBLIC' | 'PRIVATE';
|
||||
lateFeeMode: 'DAILY' | 'FIXED' | null;
|
||||
lateFeeValue: number | null;
|
||||
}
|
||||
|
||||
type EditGroupScreenProps = NativeStackScreenProps<RootStackParamList, 'EditGroup'>;
|
||||
type EditingField = keyof EditGroupPayload | null;
|
||||
// Correct props for a screen inside a nested Tab Navigator
|
||||
type EditGroupScreenProps = CompositeScreenProps<
|
||||
BottomTabScreenProps<ManagementTabParamList, 'EditGroup'>,
|
||||
NativeStackScreenProps<RootStackParamList>
|
||||
>;
|
||||
|
||||
type EditingCard = 'groupInfo' | 'financialRules' | 'configuration' | null;
|
||||
|
||||
// --- Main Screen Component ---
|
||||
const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
const { groupId } = route.params;
|
||||
|
||||
const [editingField, setEditingField] = useState<EditingField>(null);
|
||||
const [editingCard, setEditingCard] = useState<EditingCard>(null);
|
||||
const [groupDetails, setGroupDetails] = useState<GroupDetails | null>(null);
|
||||
const [editableData, setEditableData] = useState<EditGroupPayload>({
|
||||
groupName: '',
|
||||
@@ -74,30 +89,41 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
cycleType: 'MONTHLY',
|
||||
latePaymentCharge: 0,
|
||||
visibility: 'PRIVATE',
|
||||
lateFeeMode: null,
|
||||
lateFeeValue: null,
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [starting, setStarting] = useState(false);
|
||||
const [inviting, setInviting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// ... (All your handler functions: initializeEditableData, fetchGroupDetails, handleCancel, handleSave, etc. remain the same)
|
||||
const initializeEditableData = (data: GroupDetails) => {
|
||||
setEditableData({
|
||||
groupName: data.groupName,
|
||||
description: data.description,
|
||||
maxParticipants: data.maxParticipants,
|
||||
contributionAmount: data.contributionAmount,
|
||||
customCycleDays: data.customCycleDays,
|
||||
cycleType: data.cycleType,
|
||||
latePaymentCharge: data.latePaymentCharge ?? 0,
|
||||
visibility: data.visibility,
|
||||
lateFeeMode: data.lateFeeMode ?? null,
|
||||
lateFeeValue: data.lateFeeValue ?? null,
|
||||
});
|
||||
};
|
||||
|
||||
const fetchGroupDetails = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await apiCall(`https://api.godemo.in/FundCollect/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string };
|
||||
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string };
|
||||
console.log("groupDetails",response)
|
||||
if (response.status === 'success' && response.data) {
|
||||
setGroupDetails(response.data);
|
||||
setEditableData({
|
||||
groupName: response.data.groupName,
|
||||
description: response.data.description,
|
||||
maxParticipants: response.data.maxParticipants,
|
||||
contributionAmount: response.data.contributionAmount,
|
||||
customCycleDays: response.data.customCycleDays,
|
||||
cycleType: response.data.cycleType,
|
||||
latePaymentCharge: response.data.latePaymentCharge ?? 0,
|
||||
visibility: response.data.visibility,
|
||||
});
|
||||
initializeEditableData(response.data);
|
||||
} else {
|
||||
throw new Error(response.message || 'Failed to fetch group data.');
|
||||
}
|
||||
@@ -114,36 +140,24 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
|
||||
const handleCancel = () => {
|
||||
if (groupDetails) {
|
||||
setEditableData({
|
||||
groupName: groupDetails.groupName,
|
||||
description: groupDetails.description,
|
||||
maxParticipants: groupDetails.maxParticipants,
|
||||
contributionAmount: groupDetails.contributionAmount,
|
||||
customCycleDays: groupDetails.customCycleDays,
|
||||
cycleType: groupDetails.cycleType,
|
||||
latePaymentCharge: groupDetails.latePaymentCharge ?? 0,
|
||||
visibility: groupDetails.visibility,
|
||||
});
|
||||
initializeEditableData(groupDetails);
|
||||
}
|
||||
setEditingField(null);
|
||||
setEditingCard(null);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!groupDetails || !editingField) return;
|
||||
if (!groupDetails) return;
|
||||
|
||||
if (editingField === 'maxParticipants') {
|
||||
const newMaxParticipants = Number(editableData.maxParticipants);
|
||||
if (newMaxParticipants < groupDetails.totalParticipants) {
|
||||
Alert.alert('Invalid Input', `Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`);
|
||||
return;
|
||||
}
|
||||
if (editableData.maxParticipants < groupDetails.totalParticipants) {
|
||||
Alert.alert('Invalid Input', `Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`);
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
await apiCall(`https://api.godemo.in/FundCollect/api/fund/groups/${groupDetails.id}/edit`, 'PUT', editableData);
|
||||
await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/groups/${groupDetails.id}/edit`, 'PUT', editableData);
|
||||
Alert.alert('Success', 'Group has been updated.');
|
||||
setEditingField(null);
|
||||
setEditingCard(null);
|
||||
await fetchGroupDetails();
|
||||
} catch (e: any) {
|
||||
Alert.alert('Save Error', e.message || 'An unexpected error occurred.');
|
||||
@@ -165,7 +179,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
onPress: async () => {
|
||||
setStarting(true);
|
||||
try {
|
||||
await apiCall(`https://api.godemo.in/FundCollect/api/fund/group/start?groupId=${groupDetails.id}`, 'GET');
|
||||
await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/fund/group/start?groupId=${groupDetails.id}`, 'GET');
|
||||
Alert.alert('Success', 'Group has been started successfully.');
|
||||
await fetchGroupDetails();
|
||||
} catch (e: any) {
|
||||
@@ -179,124 +193,173 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const handleInvite = async () => {
|
||||
if (!groupDetails) return;
|
||||
setInviting(true);
|
||||
try {
|
||||
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/invite/generate?groupId=${groupDetails.id}`, 'GET') as { data?: string };
|
||||
|
||||
const inviteLink = response.data;
|
||||
|
||||
if (inviteLink) {
|
||||
Clipboard.setString(inviteLink);
|
||||
Alert.alert('Copied!', 'Invitation link copied to clipboard.');
|
||||
await Share.share({
|
||||
message: `Join my group on CircleCollect! ${inviteLink}`,
|
||||
url: inviteLink,
|
||||
title: 'CircleCollect Group Invitation'
|
||||
});
|
||||
} else {
|
||||
throw new Error('Could not generate invitation link.');
|
||||
}
|
||||
} catch (error: any) {
|
||||
Alert.alert('Error', error.message || 'Failed to generate invitation.');
|
||||
} finally {
|
||||
setInviting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateField = (field: keyof EditGroupPayload, value: any) => {
|
||||
setEditableData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
// ... (renderField and renderCard functions also remain the same)
|
||||
const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean) => {
|
||||
const commonInputProps = { style: styles.input, placeholderTextColor: "#9CA3AF" };
|
||||
|
||||
const getInputForField = () => {
|
||||
switch(field) {
|
||||
case 'groupName':
|
||||
return <TextInput {...commonInputProps} value={editableData.groupName} onChangeText={(text) => handleUpdateField('groupName', text)} />;
|
||||
case 'description':
|
||||
return <TextInput {...commonInputProps} style={[styles.input, styles.textArea]} value={editableData.description} onChangeText={(text) => handleUpdateField('description', text)} multiline />;
|
||||
case 'contributionAmount':
|
||||
return <TextInput {...commonInputProps} value={String(editableData.contributionAmount ?? '')} onChangeText={(text) => handleUpdateField('contributionAmount', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
||||
case 'maxParticipants':
|
||||
return <TextInput {...commonInputProps} value={String(editableData.maxParticipants ?? '')} onChangeText={(text) => handleUpdateField('maxParticipants', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
||||
case 'latePaymentCharge':
|
||||
return <TextInput {...commonInputProps} value={String(editableData.latePaymentCharge ?? '')} onChangeText={(text) => handleUpdateField('latePaymentCharge', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
||||
case 'cycleType':
|
||||
return (
|
||||
<Picker selectedValue={editableData.cycleType} onValueChange={(itemValue) => handleUpdateField('cycleType', itemValue)} style={styles.picker}>
|
||||
{['MONTHLY', 'WEEKLY', 'QUARTERLY', 'CUSTOM'].map(c => <Picker.Item key={c} label={c} value={c} />)}
|
||||
</Picker>
|
||||
);
|
||||
case 'customCycleDays':
|
||||
if (editableData.cycleType !== 'CUSTOM') return null;
|
||||
return <TextInput {...commonInputProps} value={String(editableData.customCycleDays ?? '')} onChangeText={(text) => handleUpdateField('customCycleDays', text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
||||
case 'visibility':
|
||||
return (
|
||||
<Picker selectedValue={editableData.visibility} onValueChange={(itemValue) => handleUpdateField('visibility', itemValue)} style={styles.picker}>
|
||||
<Picker.Item label="PUBLIC" value="PUBLIC" />
|
||||
<Picker.Item label="PRIVATE" value="PRIVATE" />
|
||||
</Picker>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.fieldContainer}>
|
||||
<Text style={styles.infoLabel}>{label}</Text>
|
||||
{isEditing && isEditable ? (
|
||||
<View style={{marginTop: 8}}>{getInputForField()}</View>
|
||||
) : (
|
||||
<Text style={styles.infoValue}>{displayValue}</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const renderCard = (title: string, fields: Array<{ field: keyof EditGroupPayload, label: string, displayValue: () => string | number }>, cardKey: EditingCard) => {
|
||||
const isEditingThisCard = editingCard === cardKey;
|
||||
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.cardTitle}>{title}</Text>
|
||||
{isEditable && !editingCard && (
|
||||
<TouchableOpacity onPress={() => setEditingCard(cardKey)} style={styles.editButton}>
|
||||
<Text style={styles.editButtonText}>✎</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{fields.map(({ field, label, displayValue }) =>
|
||||
renderField(field, label, displayValue(), isEditingThisCard)
|
||||
)}
|
||||
|
||||
{isEditingThisCard && (
|
||||
<View style={styles.cardActions}>
|
||||
<TouchableOpacity style={[styles.button, styles.cancelButton]} onPress={handleCancel} disabled={saving}>
|
||||
<Text style={[styles.buttonText, { color: '#374151' }]}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={[styles.button, styles.saveButton]} onPress={handleSave} disabled={saving}>
|
||||
{saving ? <ActivityIndicator color="#FFFFFF" size="small" /> : <Text style={styles.buttonText}>Save</Text>}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) return <View style={styles.centered}><ActivityIndicator size="large" color="#4F46E5" /></View>;
|
||||
if (error) return <View style={styles.centered}><Text style={styles.errorText}>{error}</Text></View>;
|
||||
if (!groupDetails) return null;
|
||||
|
||||
const isEditable = groupDetails.status === 'PENDING' || groupDetails.status === 'APPROVED';
|
||||
const isInvitable = groupDetails.status === 'APPROVED';
|
||||
const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants;
|
||||
|
||||
const renderInputField = (field: EditingField) => {
|
||||
if (!field || !editableData) return null;
|
||||
|
||||
const commonProps = { style: styles.input, placeholderTextColor: "#9CA3AF" };
|
||||
|
||||
switch(field) {
|
||||
case 'groupName':
|
||||
return <TextInput {...commonProps} value={editableData.groupName} onChangeText={(text) => handleUpdateField('groupName', text)} />;
|
||||
case 'description':
|
||||
return <TextInput {...commonProps} style={[styles.input, styles.textArea]} value={editableData.description} onChangeText={(text) => handleUpdateField('description', text)} multiline />;
|
||||
case 'contributionAmount':
|
||||
case 'maxParticipants':
|
||||
case 'latePaymentCharge':
|
||||
case 'customCycleDays':
|
||||
return <TextInput {...commonProps} value={String(editableData[field] ?? '')} onChangeText={(text) => handleUpdateField(field, text ? Number(text.replace(/[^0-9]/g, '')) : 0)} keyboardType="numeric" />;
|
||||
case 'cycleType':
|
||||
return <Picker selectedValue={editableData.cycleType} onValueChange={(itemValue) => handleUpdateField('cycleType', itemValue)} style={styles.picker}>
|
||||
{['MONTHLY', 'WEEKLY', 'QUARTERLY', 'CUSTOM'].map(c => <Picker.Item key={c} label={c} value={c} />)}
|
||||
</Picker>;
|
||||
case 'visibility':
|
||||
return <Picker selectedValue={editableData.visibility} onValueChange={(itemValue) => handleUpdateField('visibility', itemValue)} style={styles.picker}>
|
||||
<Picker.Item label="PUBLIC" value="PUBLIC" />
|
||||
<Picker.Item label="PRIVATE" value="PRIVATE" />
|
||||
</Picker>;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
const renderRow = (field: EditingField, label: string, displayValue: string | number) => {
|
||||
return (
|
||||
<View style={styles.fieldContainer}>
|
||||
<View style={styles.infoRow}>
|
||||
<Text style={styles.infoLabel}>{label}</Text>
|
||||
{editingField !== field && (
|
||||
<View style={styles.valueContainer}>
|
||||
<Text style={styles.infoValue}>{displayValue}</Text>
|
||||
{isEditable && <TouchableOpacity onPress={() => setEditingField(field)} style={styles.editButton}>
|
||||
<Text style={styles.editButtonText}>✎</Text>
|
||||
</TouchableOpacity>}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{editingField === field && isEditable && (
|
||||
<View style={styles.editingContainer}>
|
||||
{renderInputField(field)}
|
||||
<View style={styles.cardActions}>
|
||||
<TouchableOpacity style={[styles.button, styles.cancelButton]} onPress={handleCancel} disabled={saving}>
|
||||
<Text style={[styles.buttonText, { color: '#374151' }]}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={[styles.button, styles.saveButton]} onPress={handleSave} disabled={saving}>
|
||||
{saving ? <ActivityIndicator color="#FFFFFF" size="small" /> : <Text style={styles.buttonText}>Save</Text>}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<AppView paddingHorizontal={0}>
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
|
||||
<Text style={styles.headerTitle}>Edit Group</Text>
|
||||
|
||||
{canStartGroup && (
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Ready to Start?</Text>
|
||||
<Text style={styles.startGroupText}>This group has met the minimum participant requirement.</Text>
|
||||
<TouchableOpacity style={[styles.button, styles.startButton]} onPress={handleStartGroup} disabled={starting}>
|
||||
{starting ? <ActivityIndicator color="#FFFFFF" /> : <Text style={styles.buttonText}>Start Group</Text>}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
// Use AppView as the root container with flex: 1
|
||||
<AppView style={{ flex: 1, paddingHorizontal: 0 }}>
|
||||
<View style={styles.headerContainer}>
|
||||
<Text style={styles.headerTitle}>Manage Group</Text>
|
||||
{isInvitable && (
|
||||
<TouchableOpacity style={[styles.button, styles.inviteButton]} onPress={handleInvite} disabled={inviting}>
|
||||
{inviting ? <ActivityIndicator color="#FFFFFF" /> : <Text style={styles.buttonText}>Invite</Text>}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Group Info</Text>
|
||||
{renderRow('groupName', 'Group Name', groupDetails.groupName)}
|
||||
{renderRow('description', 'Description', groupDetails.description)}
|
||||
</View>
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{renderCard('Group Info', [
|
||||
{ field: 'groupName', label: 'Group Name', displayValue: () => groupDetails.groupName },
|
||||
{ field: 'description', label: 'Description', displayValue: () => groupDetails.description },
|
||||
], 'groupInfo')}
|
||||
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Financial Rules</Text>
|
||||
{renderRow('contributionAmount', 'Contribution', `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}`)}
|
||||
{renderRow('maxParticipants', 'Max Participants', `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}`)}
|
||||
{renderRow('latePaymentCharge', 'Late Fee', `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}`)}
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Configuration</Text>
|
||||
{renderRow('cycleType', 'Payment Cycle', groupDetails.cycleType)}
|
||||
{(editableData.cycleType === 'CUSTOM' || groupDetails.cycleType === 'CUSTOM') && renderRow('customCycleDays', 'Custom Days', `${editableData.customCycleDays ?? groupDetails.customCycleDays} days`)}
|
||||
{renderRow('visibility', 'Visibility', groupDetails.visibility)}
|
||||
</View>
|
||||
{renderCard('Financial Rules', [
|
||||
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
|
||||
{ field: 'maxParticipants', label: 'Max Participants', displayValue: () => `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}` },
|
||||
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
|
||||
], 'financialRules')}
|
||||
|
||||
{renderCard('Configuration', [
|
||||
{ field: 'cycleType', label: 'Payment Cycle', displayValue: () => groupDetails.cycleType },
|
||||
{ field: 'customCycleDays', label: 'Custom Days', displayValue: () => `${groupDetails.customCycleDays ?? ''} days` },
|
||||
{ field: 'visibility', label: 'Visibility', displayValue: () => groupDetails.visibility },
|
||||
], 'configuration')}
|
||||
|
||||
{/* Participants Card ... */}
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>Participants ({groupDetails.totalParticipants})</Text>
|
||||
{groupDetails.participants.length > 0 ? (
|
||||
groupDetails.participants.map(p => (
|
||||
<View key={p.id} style={styles.participantRow}>
|
||||
<View>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={styles.participantName}>{`${p.firstName} ${p.lastName || ''}`.trim()}</Text>
|
||||
{p.role === 'ORGANIZER' && <View style={styles.organizerBadge}><Text style={styles.organizerBadgeText}>Organizer</Text></View>}
|
||||
<View>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={styles.participantName}>{`${p.firstName} ${p.lastName || ''}`.trim()}</Text>
|
||||
{p.role === 'ORGANIZER' && <View style={styles.organizerBadge}><Text style={styles.organizerBadgeText}>Organizer</Text></View>}
|
||||
</View>
|
||||
<Text style={styles.participantMobile}>{p.mobile}</Text>
|
||||
</View>
|
||||
<Text style={styles.participantMobile}>{p.mobile}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
@@ -304,48 +367,104 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* --- FLOATING BUTTON AREA --- */}
|
||||
{/* This View is a sibling of ScrollView, allowing it to float on top */}
|
||||
{canStartGroup && (
|
||||
<View style={styles.floatingButtonContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.activateButton}
|
||||
onPress={handleStartGroup}
|
||||
disabled={starting}
|
||||
>
|
||||
{starting ? (
|
||||
<ActivityIndicator color="#FFFFFF" size="small" />
|
||||
) : (
|
||||
<Text style={styles.activateButtonText}>Activate Group</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</AppView>
|
||||
);
|
||||
};
|
||||
|
||||
// --- Styles ---
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#F3F4F6' },
|
||||
contentContainer: { padding: 16, paddingBottom: 40 },
|
||||
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
errorText: { color: '#EF4444', fontSize: 16, textAlign: 'center' },
|
||||
headerTitle: { fontSize: 28, fontWeight: 'bold', color: '#111827', marginBottom: 20 },
|
||||
|
||||
card: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 16, shadowColor: '#374151', shadowOpacity: 0.05, shadowRadius: 10 },
|
||||
cardTitle: { fontSize: 18, fontWeight: 'bold', color: '#1F2937', marginBottom: 12, borderBottomWidth: 1, borderBottomColor: '#E5E7EB', paddingBottom: 10 },
|
||||
|
||||
editButton: { padding: 8, marginLeft: 8 },
|
||||
editButtonText: { color: '#4F46E5', fontSize: 20, fontWeight: 'bold' },
|
||||
|
||||
fieldContainer: { borderBottomWidth: 1, borderBottomColor: '#F3F4F6', paddingVertical: 12 },
|
||||
infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
|
||||
infoLabel: { fontSize: 16, color: '#6B7280' },
|
||||
infoValue: { fontSize: 16, fontWeight: '600', color: '#111827', textTransform: 'capitalize' },
|
||||
valueContainer: { flexDirection: 'row', alignItems: 'center' },
|
||||
container: { flex: 1, backgroundColor: '#F3F4F6' },
|
||||
// Added more paddingBottom to ensure content doesn't hide behind the button AND the tab bar
|
||||
contentContainer: { padding: 16, paddingTop: 0, paddingBottom: 150 },
|
||||
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
errorText: { color: '#EF4444', fontSize: 16, textAlign: 'center' },
|
||||
headerContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 16,
|
||||
paddingBottom: 8,
|
||||
backgroundColor: 'white', // Changed for a cleaner header look
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#E5E7EB',
|
||||
},
|
||||
headerTitle: { fontSize: 28, fontWeight: 'bold', color: '#111827' },
|
||||
card: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 16, shadowColor: '#374151', shadowOpacity: 0.05, shadowRadius: 10 },
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 10,
|
||||
marginBottom: 12,
|
||||
},
|
||||
cardTitle: { fontSize: 18, fontWeight: 'bold', color: '#1F2937' },
|
||||
editButton: { padding: 8 },
|
||||
editButtonText: { color: colors.pastelPink, fontSize: 20, fontWeight: 'bold' },
|
||||
fieldContainer: { borderBottomWidth: 1, borderBottomColor: '#F3F4F6', paddingBottom: 12, marginBottom: 12 },
|
||||
infoLabel: { fontSize: 16, color: '#6B7280', marginBottom: 4 },
|
||||
infoValue: { fontSize: 16, fontWeight: '600', color: '#111827', textTransform: 'capitalize' },
|
||||
input: { backgroundColor: '#F9FAFB', borderWidth: 1, borderColor: '#D1D5DB', borderRadius: 10, padding: 14, fontSize: 16, color: '#111827' },
|
||||
textArea: { height: 100, textAlignVertical: 'top' },
|
||||
picker: { backgroundColor: '#F9FAFB', borderWidth: 1, borderColor: '#D1D5DB', borderRadius: 10 },
|
||||
participantRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' },
|
||||
participantName: { fontSize: 16, fontWeight: '600', color: '#111827' },
|
||||
participantMobile: { fontSize: 14, color: '#6B7280', marginTop: 4 },
|
||||
organizerBadge: { backgroundColor: '#FEF3C7', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 12, marginLeft: 10 },
|
||||
organizerBadgeText: { color: '#92400E', fontSize: 12, fontWeight: '500' },
|
||||
noParticipantsText: { fontSize: 15, color: '#6B7280', textAlign: 'center', paddingVertical: 20 },
|
||||
cardActions: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 20, paddingTop: 16, borderTopWidth: 1, borderTopColor: '#E5E7EB' },
|
||||
button: { borderRadius: 10, paddingVertical: 12, paddingHorizontal: 20, marginLeft: 10, minWidth: 80, alignItems: 'center', justifyContent: 'center' },
|
||||
saveButton: { backgroundColor: colors.pastelOrange },
|
||||
cancelButton: { backgroundColor: '#E5E7EB' },
|
||||
inviteButton: { backgroundColor: colors.primary },
|
||||
buttonText: { color: '#000000ff', fontSize: 15, fontWeight: '600' },
|
||||
|
||||
editingContainer: { marginTop: 12 },
|
||||
input: { backgroundColor: '#F9FAFB', borderWidth: 1, borderColor: '#D1D5DB', borderRadius: 10, padding: 14, fontSize: 16, color: '#111827' },
|
||||
textArea: { height: 100, textAlignVertical: 'top' },
|
||||
picker: { backgroundColor: '#F9FAFB', borderWidth: 1, borderColor: '#D1D5DB', borderRadius: 10, padding: 0, margin:0 },
|
||||
|
||||
participantRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#F3F4F6' },
|
||||
participantName: { fontSize: 16, fontWeight: '600', color: '#111827' },
|
||||
participantMobile: { fontSize: 14, color: '#6B7280', marginTop: 4 },
|
||||
organizerBadge: { backgroundColor: '#FEF3C7', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 12, marginLeft: 10 },
|
||||
organizerBadgeText: { color: '#92400E', fontSize: 12, fontWeight: '500' },
|
||||
noParticipantsText: { fontSize: 15, color: '#6B7280', textAlign: 'center', paddingVertical: 20 },
|
||||
|
||||
cardActions: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 20, paddingTop: 16, borderTopWidth: 1, borderTopColor: '#E5E7EB' },
|
||||
button: { borderRadius: 10, paddingVertical: 12, paddingHorizontal: 20, marginLeft: 10, minWidth: 80, alignItems: 'center', justifyContent: 'center' },
|
||||
saveButton: { backgroundColor: '#4F46E5' },
|
||||
cancelButton: { backgroundColor: '#E5E7EB' },
|
||||
startButton: { backgroundColor: '#22c55e', marginTop: 10 },
|
||||
startGroupText: { fontSize: 15, color: '#6B7280', lineHeight: 22, marginBottom: 10},
|
||||
buttonText: { color: '#FFFFFF', fontSize: 15, fontWeight: '600' },
|
||||
// --- FLOATING BUTTON STYLES ---
|
||||
floatingButtonContainer: {
|
||||
position: 'absolute',
|
||||
bottom: 85, // Positioned above the tab bar
|
||||
left: 20,
|
||||
right: 20,
|
||||
alignItems: 'center', // Center the button horizontally
|
||||
zIndex: 1, // Ensure it's on top
|
||||
},
|
||||
activateButton: {
|
||||
backgroundColor: colors.pastelOrange,
|
||||
borderRadius: 14,
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 32,
|
||||
elevation: 8,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
width: '90%', // Adjust width as needed
|
||||
alignItems: 'center',
|
||||
},
|
||||
activateButtonText: {
|
||||
color: '#000000ff',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
export default EditGroupScreen;
|
||||
|
||||
Reference in New Issue
Block a user