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

feat(JobOffer): Job offet list #17

Merged
merged 4 commits into from
Nov 26, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(JobOffer): Job offet list
thomas-mauran committed Nov 25, 2023
commit 5b45a6a8821287e827cac0195c42e8397b6ffc85
5 changes: 5 additions & 0 deletions front/assets/translations/en_US.json
Original file line number Diff line number Diff line change
@@ -6,6 +6,11 @@
"cancel": "Cancel",
"delete": "Delete"
},
"jobOffer": {
"info": {
"jobOffer": "Job offer"
}
},
"profile": {
"info": {
"contact": "Contact",
5 changes: 5 additions & 0 deletions front/assets/translations/fr_FR.json
Original file line number Diff line number Diff line change
@@ -6,6 +6,11 @@
"cancel": "Annuler",
"delete": "Supprimer"
},
"jobOffer": {
"info": {
"jobOffer": "Offre d'emploi"
}
},
"profile": {
"info": {
"contact": "Contact",
97 changes: 97 additions & 0 deletions front/src/components/jobOffers/JobOfferItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useLocales } from 'expo-localization';
import { FC } from 'react';
import { StyleSheet, View } from 'react-native';
import { Text, useTheme } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';

import { JobOffer } from '@/models/entities/jobOffer';

/**
* The styles for the JobOfferItem component.
*/
const styles = StyleSheet.create({
container: {
gap: 6,
margin: 10,
},
horizontalContainer: {
flexDirection: 'row',
},
icon: {
marginRight: 10,
marginTop: 'auto',
},
item: {
marginTop: 8,
},
nameContainer: {
alignItems: 'center',
flexDirection: 'row',
gap: 8,
},
title: {
marginBottom: 2,
},
});

/**
* The props for the JobOfferItem component.
*/
type JobOfferItemProps = {
/**
* The jobOffer to display.
*/
jobOffer: JobOffer;
};

/**
* Displays an jobOffer item.
* @constructor
*/
const JobOfferItem: FC<JobOfferItemProps> = ({ jobOffer }) => {
// Hooks
const locales = useLocales();
const theme = useTheme();

return (
<View style={styles.container}>
<View style={styles.nameContainer}>
<View>
<Text
variant='labelLarge'
style={styles.title}
>{`${jobOffer.title}`}</Text>
<Text style={styles.item}>{`${jobOffer.description}`}</Text>
</View>
</View>

<View style={[styles.horizontalContainer, styles.item]}>
<MaterialCommunityIcons
name='calendar'
size={24}
style={[styles.icon, { color: theme.colors.onSurface }]}
/>
<Text variant='labelLarge'>
{`${new Date(jobOffer.startDate).toLocaleDateString(
locales[0].languageTag,
)} - ${new Date(jobOffer.endDate).toLocaleDateString(
locales[0].languageTag,
)}`}
</Text>
</View>
<View style={[styles.horizontalContainer, styles.item]}>
<MaterialCommunityIcons
name='map-marker'
size={24}
style={[styles.icon, { color: theme.colors.onSurface }]}
/>
<Text
variant='labelLarge'
style={styles.item}
>{`${jobOffer.geographicArea}`}</Text>
</View>
</View>
);
};

export default JobOfferItem;
47 changes: 47 additions & 0 deletions front/src/components/jobOffers/JobOfferList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { FC } from 'react';
import { StyleSheet, View } from 'react-native';
import { Divider } from 'react-native-paper';

import { JobOffer } from '@/models/entities/jobOffer';

import JobOfferItem from './JobOfferItem';

/**
* The styles for the JobOfferPage component.
*/
const styles = StyleSheet.create({
divider: {
height: 1,
marginVertical: 8,
width: '100%',
},
});

/**
* The props for the JobOffer component.
*/
type JobOfferProps = {
/**
* The jobOffers to display.
*/
jobOffers: JobOffer[];
};

/**
* Displays the jobOffers of a user.
* @constructor
*/
const JobOfferList: FC<JobOfferProps> = ({ jobOffers }) => {
return (
<View>
{jobOffers?.map((jobOffer) => (
<View key={jobOffer.id}>
<JobOfferItem jobOffer={jobOffer} />
<Divider style={styles.divider} />
</View>
))}
</View>
);
};

export default JobOfferList;
39 changes: 39 additions & 0 deletions front/src/models/entities/jobOffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* A job offer.
*/
export type JobOffer = {
/**
* The id of the job offer.
*/
id: string;

/**
* The title of the job offer.
*/
title: string;

/**
* The description of the job offer.
*/
description: string;

/**
* The start date of the job offer.
*/
startDate: string;

/**
* The end date of the job offer.
*/
endDate: string;

/**
* The geographic area of the job offer.
*/
geographicArea: string;

/**
* The geographic area of the job offer.
*/
status: string;
};
8 changes: 8 additions & 0 deletions front/src/pages/internal/InternalPagesNav.tsx
Original file line number Diff line number Diff line change
@@ -4,12 +4,15 @@ import PaperNavigationBar from '@/components/utils/PaperNavigationBar';
import InternalPagesPage from '@/pages/internal/InternalPagesPage';
import ProfileNav from '@/pages/profile/ProfileNav';

import JobOffersNav from '../jobOffer/JobOfferNav';

/**
* The parameter list for the InternalPagesNav navigator.
*/
export type InternalPagesStackParamList = {
PagesMain: undefined;
PagesProfile: undefined;
PagesJobOffer: undefined;
};

const InternalPagesStack =
@@ -35,6 +38,11 @@ const InternalPagesNav = () => {
component={ProfileNav}
options={{ headerShown: false }}
/>
<InternalPagesStack.Screen
name='PagesJobOffer'
component={JobOffersNav}
options={{ headerShown: false }}
/>
</InternalPagesStack.Navigator>
);
};
7 changes: 7 additions & 0 deletions front/src/pages/internal/InternalPagesPage.tsx
Original file line number Diff line number Diff line change
@@ -34,6 +34,10 @@ const InternalPagesPage: FC<InternalPagesPageProps> = ({ navigation }) => {
navigation.navigate('PagesProfile');
}, [navigation]);

const handleJobOfferPress = useCallback(() => {
navigation.navigate('PagesJobOffer');
}, [navigation]);

return (
<ScrollView
style={styles.container}
@@ -42,6 +46,9 @@ const InternalPagesPage: FC<InternalPagesPageProps> = ({ navigation }) => {
<Button mode={'contained'} onPress={handleProfilePress}>
Profile
</Button>
<Button mode={'contained'} onPress={handleJobOfferPress}>
JobOffer
</Button>
</ScrollView>
);
};
35 changes: 35 additions & 0 deletions front/src/pages/jobOffer/JobOfferNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import PaperNavigationBar from '@/components/utils/PaperNavigationBar';
import JobOfferPage from '@/pages/jobOffer/JobOfferPage';
import i18n from '@/utils/i18n';

/**
* The parameter list for the JobOffersNav navigator.
*/
export type JobOfferStackParamList = {
JobOffer: undefined;
};

const JobOfferStack = createNativeStackNavigator<JobOfferStackParamList>();

/**
* The stack navigator for the JobOffers pages.
* @constructor
*/
const JobOffersNav = () => {
return (
<JobOfferStack.Navigator
initialRouteName='JobOffer'
screenOptions={{ header: (props) => <PaperNavigationBar {...props} /> }}
>
<JobOfferStack.Screen
name='JobOffer'
component={JobOfferPage}
options={{ headerTitle: `${i18n.t('jobOffer.info.jobOffer')}` }}
/>
</JobOfferStack.Navigator>
);
};

export default JobOffersNav;
40 changes: 40 additions & 0 deletions front/src/pages/jobOffer/JobOfferPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useFocusEffect } from '@react-navigation/native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { FC, useCallback } from 'react';

import JobOfferList from '@/components/jobOffers/JobOfferList';
import { useGetJobOffersQuery } from '@/store/api/jobApiSlice';

import { JobOfferStackParamList } from './JobOfferNav';

/**
* The props for the JobOffersPage component.
*/
type JobOfferPageProps = NativeStackScreenProps<
JobOfferStackParamList,
'JobOffer'
>;

/**
* Displays the JobOffers page for the current user.
* @constructor
*/
const JobOfferPage: FC<JobOfferPageProps> = () => {
// API calls
const { data: jobOffers, refetch: refetchJobOffers } = useGetJobOffersQuery();

// Fetch data from the API when the page is focused
useFocusEffect(
useCallback(() => {
refetchJobOffers();
}, [refetchJobOffers]),
);

if (jobOffers === undefined) {
return null;
}

return <JobOfferList jobOffers={jobOffers} />;
};

export default JobOfferPage;
1 change: 1 addition & 0 deletions front/src/store/api/apiSlice.ts
Original file line number Diff line number Diff line change
@@ -19,5 +19,6 @@ export const apiSlice = createApi({
'Profile',
'References',
'Job',
'JobOffer',
],
});
20 changes: 19 additions & 1 deletion front/src/store/api/jobApiSlice.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import { Job } from '@/models/entities/job';
import { JobCategory } from '@/models/entities/jobCategory';
import { JobOffer } from '@/models/entities/jobOffer';
import { apiSlice } from '@/store/api/apiSlice';

/**
* The extended API slice for jobs and job categories.
*/
export const extendedApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getJobOffers: builder.query<JobOffer[], void>({
query: () => 'jobOffers/',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({
type: 'JobOffer' as const,
id,
})),
{ type: 'JobOffer', id: 'LIST' },
]
: [{ type: 'JobOffer', id: 'LIST' }],
}),
getJobCategories: builder.query<JobCategory[], void>({
query: () => 'jobs/categories/',
providesTags: (result) =>
@@ -36,4 +50,8 @@ export const extendedApiSlice = apiSlice.injectEndpoints({
}),
});

export const { useGetJobCategoriesQuery, useGetJobsQuery } = extendedApiSlice;
export const {
useGetJobOffersQuery,
useGetJobCategoriesQuery,
useGetJobsQuery,
} = extendedApiSlice;