Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature create issue #40

Merged
merged 19 commits into from
Mar 12, 2024
76 changes: 76 additions & 0 deletions src/components/DropdownActionSheet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { createRef, useState, useEffect } from 'react';
import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
import ActionSheet from 'react-native-actions-sheet';
import { faAngleDown, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
import { useNavigation } from '@react-navigation/native';
import tailwind from 'tailwind';

import { getColorCode } from 'utils';

const DropdownActionSheet = ({ items, onChange, title, value }) => {
const actionSheetRef = createRef();
const navigation = useNavigation();
const [selectedItem, setSelectedItem] = useState(null);

useEffect(() => {
const selected = items.find(item => item.value === value);
if (selected) {
setSelectedItem(selected);
}
}, [value]);

const handleItemSelection = item => {
setSelectedItem(item);
onChange(item.value);
actionSheetRef.current?.hide();
};

return (
<View style={tailwind('mb-4')}>
<TouchableOpacity onPress={() => actionSheetRef.current?.setModalVisible()}>
<View style={tailwind('flex flex-row items-center justify-between pb-1 bg-gray-900 border border-gray-700 rounded-lg px-2')}>
<View style={tailwind('border-blue-700 py-2 pr-4 flex flex-row items-center')}>
<Text style={[tailwind('font-semibold text-blue-50 text-base'), selectedItem && tailwind('px-2')]}>{selectedItem ? selectedItem.label : title}</Text>
</View>
<View style={tailwind('flex flex-row items-center')}>{<FontAwesomeIcon icon={faAngleDown} style={tailwind('text-white')} />}</View>
</View>
</TouchableOpacity>
<ActionSheet
gestureEnabled={true}
bounceOnOpen={true}
nestedScrollEnabled={true}
onMomentumScrollEnd={() => actionSheetRef.current?.handleChildScrollEnd()}
ref={actionSheetRef}
containerStyle={tailwind('bg-gray-800')}
indicatorColor={getColorCode('text-gray-900')}>
<View>
<View style={tailwind('px-5 py-2 flex flex-row items-center justify-between mb-2')}>
<View style={tailwind('flex flex-row items-center')}>
<Text style={tailwind('text-lg text-white font-semibold')}>{selectedItem ? selectedItem.label : title}</Text>
</View>
<View>
<TouchableOpacity onPress={() => actionSheetRef.current?.hide()}>
<View style={tailwind('rounded-full bg-red-700 w-8 h-8 flex items-center justify-center')}>
<FontAwesomeIcon icon={faTimes} style={tailwind('text-red-100')} />
</View>
</TouchableOpacity>
</View>
</View>
<ScrollView showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false}>
{items?.map(item => (
<TouchableOpacity key={item.value} onPress={() => handleItemSelection(item)}>
<View style={tailwind('flex flex-row items-center px-5 py-4 border-b border-gray-900')}>
<Text style={tailwind('font-semibold text-lg text-gray-100')}>{item.label}</Text>
</View>
</TouchableOpacity>
))}
<View style={tailwind('w-full h-40')}></View>
</ScrollView>
</View>
</ActionSheet>
</View>
);
};

export default DropdownActionSheet;
28 changes: 28 additions & 0 deletions src/constant/Enum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const IssueType = {
VEHICLE: 'Vehicle',
DRIVER: 'Driver',
ROUTE: 'Route',
PAYLOAD_CARGO: 'Payload Cargo',
SOFTWARE_TECHNICAL: 'Software Technical',
OPERATIONAL: 'Operational',
CUSTOMER: 'Customer',
SECURITY: 'Security',
ENVIRONMENTAL_SUSTAINABILITY: 'Environmental Sustainability',
};
const IssueCategory = {
Compliance: 'Compliance',
ResourceAllocation: 'Resource Allocation',
CostOverruns: 'Cost Overruns',
Communication: 'Communication',
VendorManagementIssue: 'Vendor Management Issue',
};

const IssuePriority = {
LOW: 'Low',
MEDIUM: 'Medium',
HIGH: 'High',
CRITICAL: 'Critical',
SCHEDULED_MAINTENANCE: 'Scheduled Maintenance',
OPERATIONAL_SUGGESTION: 'Operational Suggestion',
};
export { IssuePriority, IssueType, IssueCategory };
20 changes: 20 additions & 0 deletions src/constant/GetIssueCategoy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export default function getIssueCategories(type = null, options) {
const issueCategories = {
VEHICLE: ['Mechanical Problems', 'Cosmetic Damages', 'Tire Issues', 'Electronics and Instruments', 'Maintenance Alerts', 'Fuel Efficiency Issues'],
DRIVER: ['Behavior Concerns', 'Documentation', 'Time Management', 'Communication', 'Training Needs', 'Health and Safety Violations'],
ROUTE: ['Inefficient Routes', 'Safety Concerns', 'Blocked Routes', 'Environmental Considerations', 'Unfavorable Weather Conditions'],
PAYLOAD_CARGO: ['Damaged Goods', 'Misplaced Goods', 'Documentation Issues', 'Temperature-Sensitive Goods', 'Incorrect Cargo Loading'],
SOFTWARE_TECHNICAL: ['Bugs', 'UI/UX Concerns', 'Integration Failures', 'Performance', 'Feature Requests', 'Security Vulnerabilities'],
OPERATIONAL: ['Compliance', 'Resource Allocation', 'Cost Overruns', 'Communication', 'Vendor Management Issues'],
CUSTOMER: ['Service Quality', 'Billing Discrepancies', 'Communication Breakdown', 'Feedback and Suggestions', 'Order Errors'],
SECURITY: ['Unauthorized Access', 'Data Concerns', 'Physical Security', 'Data Integrity Issues'],
ENVIRONMENTAL_SUSTAINABILITY: ['Fuel Consumption', 'Carbon Footprint', 'Waste Management', 'Green Initiatives Opportunities'],
};

if (type) {
return issueCategories[type] || [];
}

const allIssueCategories = Object.values(issueCategories).flat();
return allIssueCategories;
}
2 changes: 1 addition & 1 deletion src/features/Account/AccountStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const AccountStack = ({ route }) => {
<RootStack.Screen name="SearchScreen" component={SearchScreen} options={{ headerShown: false }} initialParams={route.params ?? {}} />
<RootStack.Screen name="ConfigScreen" component={ConfigScreen} options={{ headerShown: false }} />
<RootStack.Screen name="Organization" component={OrganizationScreen} options={{ headerShown: false }} />
</RootStack.Navigator>
</RootStack.Navigator>
</SafeAreaProvider>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/features/Account/screens/AccountScreen.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { faBuilding, faChevronRight, faIdBadge, faLink, faUser } from '@fortawesome/free-solid-svg-icons';
import { faBuilding, faChevronRight, faIdBadge, faLink, faUser, faFileAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
import { useFleetbase } from 'hooks';
import React, { useEffect, useState } from 'react';
Expand Down
2 changes: 2 additions & 0 deletions src/features/Core/MainStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import OrderScreen from 'shared/OrderScreen';
import ProofScreen from 'shared/ProofScreen';
import MainScreen from './screens/MainScreen';
import SearchScreen from './screens/SearchScreen';
import IssueScreen from './screens/IssueScreen';

const RootStack = createStackNavigator();

Expand All @@ -28,6 +29,7 @@ const MainStack = ({ route }) => {
<RootStack.Screen name="EntityScreen" component={EntityScreen} options={{ headerShown: false }} initialParams={route.params ?? {}} />
<RootStack.Screen name="ProofScreen" component={ProofScreen} options={{ headerShown: false }} initialParams={route.params ?? {}} />
<RootStack.Screen name="SearchScreen" component={SearchScreen} options={{ headerShown: false }} initialParams={route.params ?? {}} />
<RootStack.Screen name="IssueScreen" component={IssueScreen} options={{ headerShown: false }} initialParams={route.params ?? {}} />
</RootStack.Group>
</RootStack.Navigator>
</SafeAreaProvider>
Expand Down
239 changes: 239 additions & 0 deletions src/features/Core/screens/IssueScreen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
import { IssuePriority, IssueType } from 'constant/Enum';
import { useDriver, useFleetbase } from 'hooks';
import React, { useEffect, useState } from 'react';
import { ActivityIndicator, Alert, Keyboard, KeyboardAvoidingView, Pressable, Text, TextInput, TouchableOpacity, View } from 'react-native';
import Toast from 'react-native-toast-message';
import tailwind from 'tailwind';
import { getColorCode, getCurrentLocation, logError, translate } from 'utils';
import DropdownActionSheet from '../../../components/DropdownActionSheet';
import getIssueCategories from '../../../constant/GetIssueCategoy';

const IssueScreen = ({ navigation, route }) => {
const issue = route.params;
const isEdit = route.params;
const [isLoading, setIsLoading] = useState(false);
const fleetbase = useFleetbase();
const [driver] = useDriver();
const [driverId] = useState(driver.getAttribute('id'));

const [type, setType] = useState(issue.type);
const [categories, setCategories] = useState([]);
const [category, setCategory] = useState();
const [priority, setPriority] = useState();
const [report, setReport] = useState(issue.report);
const [error, setError] = useState('');

useEffect(() => {
if (issue) {
setCategory(issue.issue?.category);
setPriority(issue.issue?.priority);
setReport(issue.issue?.report);
setType(issue.issue?.type);
}
}, []);

useEffect(() => {
if (!type) return;

setCategories(getIssueCategories(type));
}, [type]);

const saveIssue = () => {
if (!validateInputs()) {
return;
}
setIsLoading(true);
const location = getCurrentLocation().then();
const adapter = fleetbase.getAdapter();

if (issue.issue?.id) {
adapter
.put(`issues/${issue.issue.id}`, {
type,
category,
priority,
report,
location: location,
driver: driverId,
})
.then(() => {
Toast.show({
type: 'success',
text1: `Successfully updated`,
});
setIsLoading(false);
navigation.goBack();
})
.catch(error => {
setIsLoading(false);
logError(error);
});
} else {
adapter
.post('issues', {
type,
category,
priority,
report,
location: location,
driver: driverId,
})
.then(() => {
Toast.show({
type: 'success',
text1: `Successfully created`,
});
setIsLoading(false);
navigation.goBack();
})
.catch(error => {
setIsLoading(false);
logError(error);
});
}
};

const deleteIssues = () => {
Alert.alert('Confirmation', 'Are you sure you want to delete this issue?', [
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Delete',
onPress: () => confirmDelete(),
},
]);
};

const confirmDelete = () => {
const adapter = fleetbase.getAdapter();
adapter
.delete(`issues/${issue.issue.id}`)
.then(() => {
Toast.show({
type: 'success',
text1: `Successfully deleted`,
});
setIsLoading(false);
navigation.goBack();
})
.catch(error => {
setIsLoading(false);
logError(error);
});
};

const validateInputs = () => {
if (!type || !category || !priority || !report?.trim()) {
setError('Please enter a required value.');
return false;
} else if (report.trim().length === 0) {
setError('Report cannot be empty.');
return false;
}
setError('');
return true;
};

return (
<View style={[tailwind('w-full h-full bg-gray-800')]}>
<Pressable onPress={Keyboard.dismiss} style={tailwind('w-full h-full relative')}>
<View style={tailwind('flex flex-row items-center justify-between p-4')}>
{issue.isEdit ? (
<Text style={tailwind('text-xl text-gray-50 font-semibold')}>{translate('Core.IssueScreen.update')}</Text>
) : (
<Text style={tailwind('text-xl text-gray-50 font-semibold')}>{translate('Core.IssueScreen.create')}</Text>
)}
<TouchableOpacity onPress={() => navigation.goBack()} style={tailwind('mr-4')}>
<View style={tailwind('rounded-full bg-gray-900 w-10 h-10 flex items-center justify-center')}>
<FontAwesomeIcon icon={faTimes} style={tailwind('text-red-400')} />
</View>
</TouchableOpacity>
</View>
<View style={tailwind('flex w-full h-full')}>
<KeyboardAvoidingView style={tailwind('p-4')}>
<View style={isEdit.isEdit ? tailwind('flex flex-row items-center justify-between pb-1') : {}}>
<Text style={tailwind('font-semibold text-base text-gray-50 mb-2')}>{translate('Core.IssueScreen.type')}</Text>
{isEdit.isEdit ? (
<Text style={tailwind('text-white')}>{type}</Text>
) : (
<DropdownActionSheet
value={type}
items={Object.keys(IssueType).map(type => {
return { label: IssueType[type], value: type };
})}
onChange={setType}
title={translate('Core.IssueScreen.selectType')}
/>
)}

{error && !type ? <Text style={tailwind('text-red-500 mb-2')}>{error}</Text> : null}
</View>

<View style={isEdit.isEdit ? tailwind('flex flex-row items-center justify-between pb-1') : {}}>
<Text style={tailwind('font-semibold text-base text-gray-50 mb-2')}>{translate('Core.IssueScreen.category')}</Text>
{isEdit.isEdit ? (
<Text style={tailwind('text-white')}>{category}</Text>
) : (
<DropdownActionSheet
value={category}
items={categories?.map(category => {
return { label: category, value: category };
})}
onChange={setCategory}
title={'Select category'}
/>
)}
{error && !category ? <Text style={tailwind('text-red-500 mb-2')}>{error}</Text> : null}
</View>
<View style={tailwind('mb-4')}>
<Text style={tailwind('font-semibold text-base text-gray-50 mb-2')}>{translate('Core.IssueScreen.report')}</Text>
<TextInput
value={report}
onChangeText={setReport}
numberOfLines={4}
multiline={true}
placeholder={translate('Core.IssueScreen.enterReport')}
placeholderTextColor={getColorCode('text-gray-600')}
style={tailwind('form-input text-white h-28')}
/>
{error && !report?.trim() ? <Text style={tailwind('text-red-500 mb-2')}>{error}</Text> : null}
</View>
<View>
<Text style={tailwind('font-semibold text-base text-gray-50 mb-2')}>{translate('Core.IssueScreen.priority')}</Text>
<DropdownActionSheet
value={priority}
items={Object.keys(IssuePriority).map(priority => {
return { label: IssuePriority[priority], value: priority };
})}
onChange={setPriority}
title={translate('Core.IssueScreen.selectPriority')}
/>

{error && !priority ? <Text style={tailwind('text-red-500 mb-2')}>{error}</Text> : null}
</View>

<TouchableOpacity onPress={saveIssue} disabled={isLoading} style={tailwind('flex')}>
<View style={tailwind('btn bg-gray-900 border border-gray-700 mt-4 ')}>
{isLoading && <ActivityIndicator color={getColorCode('text-gray-50')} style={tailwind('mr-2')} />}
<Text style={tailwind('font-semibold text-lg text-gray-50 text-center')}>{translate('Core.IssueScreen.save')}</Text>
</View>
</TouchableOpacity>
{isEdit.isEdit && (
<TouchableOpacity onPress={deleteIssues} disabled={isLoading} style={tailwind('flex')}>
<View style={tailwind('btn bg-gray-900 border border-gray-700 mt-4')}>
<Text style={tailwind('font-semibold text-lg text-gray-50 text-center')}>Delete</Text>
</View>
</TouchableOpacity>
)}
</KeyboardAvoidingView>
</View>
</Pressable>
</View>
);
};

export default IssueScreen;
Loading
Loading