Fakultas Ilmu Komputer UI

Commit 8854d559 authored by Wulan Mantiri's avatar Wulan Mantiri Committed by Muzaki Azami
Browse files

Integrate client profile with read only diet recommendation API

parent 5d91ad27
export const mockDietRecommendation = {
id: 1,
client_plan_meal: '',
nutritional_advice: 'good job',
lifestyle_advice: 'haiya',
nutritionist: 1,
client: 1,
};
...@@ -8,22 +8,24 @@ import { ...@@ -8,22 +8,24 @@ import {
Questionnaire3, Questionnaire3,
Questionnaire4, Questionnaire4,
Questionnaire5, Questionnaire5,
ClientProfile,
// Others // Public
AllAccessQuestionnaire, AllAccessQuestionnaire,
Checkout,
ChoosePlan, ChoosePlan,
DietelaQuizResult, DietelaQuizResult,
InitialPage, InitialPage,
ManualRegistrationPage, ManualRegistrationPage,
LoginPage, LoginPage,
NutritionistAdminLogin,
ProgramDetail, ProgramDetail,
NutritionistDetail, NutritionistDetail,
// Private
Checkout,
PaymentResult, PaymentResult,
ClientListNutritionist, ClientListNutritionist,
ReadOnlyNutritionistRecommendation, ClientProfile,
NutritionistAdminLogin, ClientProfileForAdmin,
} from 'scenes'; } from 'scenes';
import { FC } from 'react'; import { FC } from 'react';
...@@ -166,7 +168,7 @@ export const clientNavigation: NavRoute[] = [ ...@@ -166,7 +168,7 @@ export const clientNavigation: NavRoute[] = [
export const adminNavigation: NavRoute[] = [ export const adminNavigation: NavRoute[] = [
{ {
name: ROUTES.clientProfile, name: ROUTES.clientProfile,
component: ReadOnlyNutritionistRecommendation, component: ClientProfileForAdmin,
header: 'Profil Klien', header: 'Profil Klien',
}, },
]; ];
......
...@@ -2,7 +2,7 @@ import { downloadFile, DocumentDirectoryPath } from 'react-native-fs'; ...@@ -2,7 +2,7 @@ import { downloadFile, DocumentDirectoryPath } from 'react-native-fs';
import { Toast } from 'components/core'; import { Toast } from 'components/core';
import { PermissionsAndroid } from 'react-native'; import { PermissionsAndroid } from 'react-native';
const useDownloadFiles = (url: string) => { const useDownloadFiles = (url = '') => {
const fileName = url.split('/').pop(); const fileName = url.split('/').pop();
const extension = fileName?.split('.').pop()?.toUpperCase() || '-'; const extension = fileName?.split('.').pop()?.toUpperCase() || '-';
......
...@@ -9,7 +9,6 @@ export { default as AllAccessQuestionnaire } from './questionnaire/AllAccessQues ...@@ -9,7 +9,6 @@ export { default as AllAccessQuestionnaire } from './questionnaire/AllAccessQues
export { default as DietelaQuizResult } from './questionnaire/DietelaQuizResult'; export { default as DietelaQuizResult } from './questionnaire/DietelaQuizResult';
export { default as ExtendedQuestionnaire } from './questionnaire/ExtendedQuestionnaire'; export { default as ExtendedQuestionnaire } from './questionnaire/ExtendedQuestionnaire';
export * from './questionnaire/ExtendedQuestionnaire/components'; export * from './questionnaire/ExtendedQuestionnaire/components';
export * from './questionnaire/NutritionistRecommendation';
export { default as Checkout } from './cart/Checkout'; export { default as Checkout } from './cart/Checkout';
export { default as ChoosePlan } from './cart/ChoosePlan'; export { default as ChoosePlan } from './cart/ChoosePlan';
...@@ -21,3 +20,4 @@ export { default as PaymentResult } from './payment/PaymentResult'; ...@@ -21,3 +20,4 @@ export { default as PaymentResult } from './payment/PaymentResult';
export { default as ClientListNutritionist } from './nutritionist/ClientListNutritionist'; export { default as ClientListNutritionist } from './nutritionist/ClientListNutritionist';
export { default as ClientProfile } from './profile/ClientProfile'; export { default as ClientProfile } from './profile/ClientProfile';
export { default as ClientProfileForAdmin } from './profile/ClientProfileForAdmin';
import React from 'react'; import React from 'react';
import { render, fireEvent } from '@testing-library/react-native'; import { render, fireEvent, waitFor } from '@testing-library/react-native';
import axios from 'axios';
import ClientProfile from '.'; import ClientProfile from '.';
import { mockDietRecommendation } from '__mocks__/dietRecommendation';
jest.mock('axios');
const mockAxios = axios as jest.Mocked<typeof axios>;
const mockedNavigate = jest.fn(); const mockedNavigate = jest.fn();
...@@ -14,9 +19,35 @@ jest.mock('@react-navigation/native', () => { ...@@ -14,9 +19,35 @@ jest.mock('@react-navigation/native', () => {
}); });
describe('ClientProfile', () => { describe('ClientProfile', () => {
it('renders correctly', () => { const retrievedietRecommendationApi = () =>
Promise.resolve({
status: 200,
data: [mockDietRecommendation],
});
it('fetches data from backend and renders correctly', async () => {
mockAxios.request.mockImplementationOnce(retrievedietRecommendationApi);
render(<ClientProfile />);
await waitFor(() => expect(mockAxios.request).toBeCalled());
});
it('fetches empty list from backend and still renders correctly', async () => {
mockAxios.request.mockImplementationOnce(() =>
Promise.resolve({
status: 200,
data: [],
}),
);
render(<ClientProfile />);
await waitFor(() => expect(mockAxios.request).toBeCalled());
});
it('redirects to extended questionnaire when "Ubah profil" button is pressed', async () => {
mockAxios.request.mockImplementationOnce(retrievedietRecommendationApi);
const { getByText } = render(<ClientProfile />); const { getByText } = render(<ClientProfile />);
await waitFor(() => expect(mockAxios.request).toBeCalled());
const changeProfileButton = getByText(/ubah profil/i); const changeProfileButton = getByText(/ubah profil/i);
expect(changeProfileButton).toBeTruthy(); expect(changeProfileButton).toBeTruthy();
fireEvent.press(changeProfileButton); fireEvent.press(changeProfileButton);
......
...@@ -2,18 +2,33 @@ import React, { FC } from 'react'; ...@@ -2,18 +2,33 @@ import React, { FC } from 'react';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { Button } from 'react-native-elements'; import { Button } from 'react-native-elements';
import { Loader } from 'components/core';
import * as ROUTES from 'constants/routes'; import * as ROUTES from 'constants/routes';
import { ReadOnlyNutritionistRecommendation } from 'scenes/questionnaire/NutritionistRecommendation'; import ReadOnlyDietRecommendation from 'scenes/questionnaire/ReadOnlyDietRecommendation';
import { Section } from 'components/layout'; import { Section } from 'components/layout';
import { typographyStyles } from 'styles'; import { typographyStyles } from 'styles';
import { useApi } from 'hooks';
import { retrieveDietRecommendationApi } from 'services/dietRecommendation';
import { styles } from './styles'; import { styles } from './styles';
const ClientProfile: FC = () => { const ClientProfile: FC = () => {
const navigation = useNavigation(); const navigation = useNavigation();
const { isLoading, data: recommendation } = useApi(
retrieveDietRecommendationApi,
);
if (isLoading) {
return <Loader />;
}
return ( return (
<ReadOnlyNutritionistRecommendation> <ReadOnlyDietRecommendation
data={
recommendation && recommendation.length > 0
? recommendation[0]
: undefined
}>
<Section> <Section>
<Button <Button
title="ubah profil" title="ubah profil"
...@@ -23,7 +38,7 @@ const ClientProfile: FC = () => { ...@@ -23,7 +38,7 @@ const ClientProfile: FC = () => {
titleStyle={[typographyStyles.overlineBig, styles.titleStyle]} titleStyle={[typographyStyles.overlineBig, styles.titleStyle]}
/> />
</Section> </Section>
</ReadOnlyNutritionistRecommendation> </ReadOnlyDietRecommendation>
); );
}; };
......
import React from 'react';
import { render, waitFor } from '@testing-library/react-native';
import axios from 'axios';
import ClientProfileForAdmin from '.';
import { mockDietRecommendation } from '__mocks__/dietRecommendation';
jest.mock('axios');
const mockAxios = axios as jest.Mocked<typeof axios>;
jest.mock('@react-navigation/native', () => {
return {
useRoute: () => ({
params: {
id: 1,
},
}),
};
});
describe('ClientProfileForAdmin', () => {
const retrievedietRecommendationByIdApi = () =>
Promise.resolve({
status: 200,
data: mockDietRecommendation,
});
it('fetches data from backend and renders correctly', async () => {
mockAxios.request.mockImplementationOnce(retrievedietRecommendationByIdApi);
render(<ClientProfileForAdmin />);
await waitFor(() => expect(mockAxios.request).toBeCalled());
});
});
import React, { FC } from 'react';
import { useRoute } from '@react-navigation/native';
import { Loader } from 'components/core';
import ReadOnlyDietRecommendation from 'scenes/questionnaire/ReadOnlyDietRecommendation';
import { useApi } from 'hooks';
import { retrieveDietRecommendationByIdApi } from 'services/dietRecommendation';
import { DietRecommendationResponse } from 'services/dietRecommendation/models';
const ClientProfileForAdmin: FC = () => {
const route = useRoute();
const { id } = route.params as DietRecommendationResponse;
const { isLoading, data } = useApi(() =>
retrieveDietRecommendationByIdApi(id),
);
if (isLoading) {
return <Loader />;
}
return <ReadOnlyDietRecommendation data={data} />;
};
export default ClientProfileForAdmin;
import React from 'react';
import { render } from '@testing-library/react-native';
import ReadOnlyNutritionistRecommendation from '.';
describe('ReadOnlyNutritionistRecommendation', () => {
it('renders correctly', () => {
render(<ReadOnlyNutritionistRecommendation />);
});
});
export { default as ReadOnlyNutritionistRecommendation } from './ReadOnly';
import React from 'react';
import { render } from '@testing-library/react-native';
import ReadOnlyDietRecommendation from '.';
import { mockDietRecommendation } from '__mocks__/dietRecommendation';
describe('ReadOnlyDietRecommendation', () => {
it('shows "Belum ada rekomendasi" text if no recommendation is provided yet', () => {
const { getByText } = render(<ReadOnlyDietRecommendation />);
expect(getByText(/Belum ada rekomendasi/i)).toBeTruthy();
});
it('shows recommendation when provided', () => {
const { getByText } = render(
<ReadOnlyDietRecommendation data={mockDietRecommendation} />,
);
expect(getByText(/haiya/i)).toBeTruthy();
});
});
import React, { FC } from 'react'; import React, { FC } from 'react';
import { ScrollView, View } from 'react-native'; import { ScrollView, View } from 'react-native';
import { Text, Button } from 'react-native-elements'; import { Text, Button, Icon } from 'react-native-elements';
import { WebView } from 'react-native-webview'; import { WebView } from 'react-native-webview';
import { InfoCard, Loader } from 'components/core'; import { InfoCard, Loader } from 'components/core';
import { layoutStyles } from 'styles'; import { layoutStyles, colors } from 'styles';
import { useDownloadFiles } from 'hooks'; import { useDownloadFiles } from 'hooks';
import { styles } from './styles'; import { styles } from './styles';
import { Props } from './types';
const ReadOnlyNutritionistRecommendation: FC = ({ children }) => { const ReadOnlyDietRecommendation: FC<Props> = ({ children, data }) => {
const url = 'http://www.africau.edu/images/default/sample.pdf'; const { download, pdfViewUrl, fileName } = useDownloadFiles(
data?.client_plan_meal,
const { download, pdfViewUrl, fileName } = useDownloadFiles(url); );
if (!data) {
return (
<View style={styles.center}>
<Icon
name="emoticon-sad-outline"
type="material-community"
size={75}
color={colors.danger}
/>
<Text style={styles.noRecomText}>
Belum ada rekomendasi dari nutritionis
</Text>
</View>
);
}
return ( return (
<ScrollView contentContainerStyle={layoutStyles}> <ScrollView contentContainerStyle={layoutStyles}>
<Text style={styles.header}>Saran Gizi</Text> <Text style={styles.header}>Saran Gizi</Text>
<InfoCard content="Anda butuh sayurand buahde fhhehfkwhefkuhbweijkfcbewujkfdc wjdbf" /> <InfoCard content={data.nutritional_advice} />
<Text style={[styles.header, styles.spacing]}>Saran Gaya Hidup</Text> <Text style={[styles.header, styles.spacing]}>Saran Gaya Hidup</Text>
<InfoCard content="Anda butuh istirahat" /> <InfoCard content={data.lifestyle_advice} />
<View style={styles.spacing}> <View style={styles.spacing}>
<Text style={styles.header}>Rencana Menu dan Porsi Makan</Text> <Text style={styles.header}>Rencana Menu dan Porsi Makan</Text>
</View> </View>
...@@ -45,4 +61,4 @@ const ReadOnlyNutritionistRecommendation: FC = ({ children }) => { ...@@ -45,4 +61,4 @@ const ReadOnlyNutritionistRecommendation: FC = ({ children }) => {
); );
}; };
export default ReadOnlyNutritionistRecommendation; export default ReadOnlyDietRecommendation;
...@@ -12,7 +12,6 @@ export const styles = StyleSheet.create({ ...@@ -12,7 +12,6 @@ export const styles = StyleSheet.create({
}, },
pdfView: { pdfView: {
height: Dimensions.get('window').height * 0.6, height: Dimensions.get('window').height * 0.6,
flex: 1,
marginBottom: 10, marginBottom: 10,
}, },
buttonStyle: { buttonStyle: {
...@@ -22,4 +21,15 @@ export const styles = StyleSheet.create({ ...@@ -22,4 +21,15 @@ export const styles = StyleSheet.create({
color: 'black', color: 'black',
paddingRight: 6, paddingRight: 6,
}, },
center: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 30,
},
noRecomText: {
...typography.headingMedium,
textAlign: 'center',
marginTop: 10,
},
}); });
import { DietRecommendationResponse } from 'services/dietRecommendation/models';
export interface Props {
data?: DietRecommendationResponse;
}
import { api, RequestMethod, ApiResponse } from '../api';
import * as apiUrls from './urls';
import { DietRecommendationResponse } from './models';
export const retrieveDietRecommendationApi = (): ApiResponse<
DietRecommendationResponse[]
> => {
return api(RequestMethod.GET, apiUrls.dietRecommendation);
};
export const retrieveDietRecommendationByIdApi = (
id: number,
): ApiResponse<DietRecommendationResponse> => {
return api(RequestMethod.GET, apiUrls.dietRecommendationById(id));
};
export interface DietRecommendationResponse {
id: number;
client_plan_meal: string;
nutritional_advice: string;
lifestyle_advice: string;
nutritionist: number;
client: number;
}
export const dietRecommendation = 'diet-recommendation/';
export const dietRecommendationById = (id: number) =>
`${dietRecommendation}${id}/`;
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment