Fakultas Ilmu Komputer UI

Commit 94bfdc25 authored by Muzaki Azami's avatar Muzaki Azami
Browse files

Merge branch 'PBI-11-profile_client_api_integration' into 'staging'

Integrate client profile with read only diet recommendation API

See merge request !48
parents 5d91ad27 8854d559
Pipeline #76480 passed with stages
in 49 minutes and 17 seconds
export const mockDietRecommendation = {
id: 1,
client_plan_meal: '',
nutritional_advice: 'good job',
lifestyle_advice: 'haiya',
nutritionist: 1,
client: 1,
};
......@@ -8,22 +8,24 @@ import {
Questionnaire3,
Questionnaire4,
Questionnaire5,
ClientProfile,
// Others
// Public
AllAccessQuestionnaire,
Checkout,
ChoosePlan,
DietelaQuizResult,
InitialPage,
ManualRegistrationPage,
LoginPage,
NutritionistAdminLogin,
ProgramDetail,
NutritionistDetail,
// Private
Checkout,
PaymentResult,
ClientListNutritionist,
ReadOnlyNutritionistRecommendation,
NutritionistAdminLogin,
ClientProfile,
ClientProfileForAdmin,
} from 'scenes';
import { FC } from 'react';
......@@ -166,7 +168,7 @@ export const clientNavigation: NavRoute[] = [
export const adminNavigation: NavRoute[] = [
{
name: ROUTES.clientProfile,
component: ReadOnlyNutritionistRecommendation,
component: ClientProfileForAdmin,
header: 'Profil Klien',
},
];
......
......@@ -2,7 +2,7 @@ import { downloadFile, DocumentDirectoryPath } from 'react-native-fs';
import { Toast } from 'components/core';
import { PermissionsAndroid } from 'react-native';
const useDownloadFiles = (url: string) => {
const useDownloadFiles = (url = '') => {
const fileName = url.split('/').pop();
const extension = fileName?.split('.').pop()?.toUpperCase() || '-';
......
......@@ -9,7 +9,6 @@ export { default as AllAccessQuestionnaire } from './questionnaire/AllAccessQues
export { default as DietelaQuizResult } from './questionnaire/DietelaQuizResult';
export { default as ExtendedQuestionnaire } from './questionnaire/ExtendedQuestionnaire';
export * from './questionnaire/ExtendedQuestionnaire/components';
export * from './questionnaire/NutritionistRecommendation';
export { default as Checkout } from './cart/Checkout';
export { default as ChoosePlan } from './cart/ChoosePlan';
......@@ -21,3 +20,4 @@ export { default as PaymentResult } from './payment/PaymentResult';
export { default as ClientListNutritionist } from './nutritionist/ClientListNutritionist';
export { default as ClientProfile } from './profile/ClientProfile';
export { default as ClientProfileForAdmin } from './profile/ClientProfileForAdmin';
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 { mockDietRecommendation } from '__mocks__/dietRecommendation';
jest.mock('axios');
const mockAxios = axios as jest.Mocked<typeof axios>;
const mockedNavigate = jest.fn();
......@@ -14,9 +19,35 @@ jest.mock('@react-navigation/native', () => {
});
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 />);
await waitFor(() => expect(mockAxios.request).toBeCalled());
const changeProfileButton = getByText(/ubah profil/i);
expect(changeProfileButton).toBeTruthy();
fireEvent.press(changeProfileButton);
......
......@@ -2,18 +2,33 @@ import React, { FC } from 'react';
import { useNavigation } from '@react-navigation/native';
import { Button } from 'react-native-elements';
import { Loader } from 'components/core';
import * as ROUTES from 'constants/routes';
import { ReadOnlyNutritionistRecommendation } from 'scenes/questionnaire/NutritionistRecommendation';
import ReadOnlyDietRecommendation from 'scenes/questionnaire/ReadOnlyDietRecommendation';
import { Section } from 'components/layout';
import { typographyStyles } from 'styles';
import { useApi } from 'hooks';
import { retrieveDietRecommendationApi } from 'services/dietRecommendation';
import { styles } from './styles';
const ClientProfile: FC = () => {
const navigation = useNavigation();
const { isLoading, data: recommendation } = useApi(
retrieveDietRecommendationApi,
);
if (isLoading) {
return <Loader />;
}
return (
<ReadOnlyNutritionistRecommendation>
<ReadOnlyDietRecommendation
data={
recommendation && recommendation.length > 0
? recommendation[0]
: undefined
}>
<Section>
<Button
title="ubah profil"
......@@ -23,7 +38,7 @@ const ClientProfile: FC = () => {
titleStyle={[typographyStyles.overlineBig, styles.titleStyle]}
/>
</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 { 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 { InfoCard, Loader } from 'components/core';
import { layoutStyles } from 'styles';
import { layoutStyles, colors } from 'styles';
import { useDownloadFiles } from 'hooks';
import { styles } from './styles';
import { Props } from './types';
const ReadOnlyNutritionistRecommendation: FC = ({ children }) => {
const url = 'http://www.africau.edu/images/default/sample.pdf';
const { download, pdfViewUrl, fileName } = useDownloadFiles(url);
const ReadOnlyDietRecommendation: FC<Props> = ({ children, data }) => {
const { download, pdfViewUrl, fileName } = useDownloadFiles(
data?.client_plan_meal,
);
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 (
<ScrollView contentContainerStyle={layoutStyles}>
<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>
<InfoCard content="Anda butuh istirahat" />
<InfoCard content={data.lifestyle_advice} />
<View style={styles.spacing}>
<Text style={styles.header}>Rencana Menu dan Porsi Makan</Text>
</View>
......@@ -45,4 +61,4 @@ const ReadOnlyNutritionistRecommendation: FC = ({ children }) => {
);
};
export default ReadOnlyNutritionistRecommendation;
export default ReadOnlyDietRecommendation;
......@@ -12,7 +12,6 @@ export const styles = StyleSheet.create({
},
pdfView: {
height: Dimensions.get('window').height * 0.6,
flex: 1,
marginBottom: 10,
},
buttonStyle: {
......@@ -22,4 +21,15 @@ export const styles = StyleSheet.create({
color: 'black',
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