Fakultas Ilmu Komputer UI

Commit 8c52b868 authored by Kefas Satrio Bangkit Solidedantyo's avatar Kefas Satrio Bangkit Solidedantyo
Browse files

Allow update for nutritionist comments

parent 4cc5c8f8
...@@ -42,6 +42,8 @@ export const dietReportTextFields: { [_: string]: TextFieldSchema[] } = { ...@@ -42,6 +42,8 @@ export const dietReportTextFields: { [_: string]: TextFieldSchema[] } = {
name: 'water_consumption', name: 'water_consumption',
required: true, required: true,
placeholder: 'ex: 8', placeholder: 'ex: 8',
fieldType: FieldType.NUMBER,
max: 20,
keyboardType: 'numeric', keyboardType: 'numeric',
errorMessage: 'Konsumsi minuman', errorMessage: 'Konsumsi minuman',
}, },
......
...@@ -38,6 +38,8 @@ export const mockUserReportResponse: UserReportResponse = { ...@@ -38,6 +38,8 @@ export const mockUserReportResponse: UserReportResponse = {
name: 'Shin Ryujin', name: 'Shin Ryujin',
email: 'ryujin@itzy.com', email: 'ryujin@itzy.com',
role: UserRole.CLIENT, role: UserRole.CLIENT,
phone_number: null,
deadline: null,
}, },
...mockUserReportRequest, ...mockUserReportRequest,
}; };
...@@ -60,3 +62,28 @@ export const mockUserReportHistory = { ...@@ -60,3 +62,28 @@ export const mockUserReportHistory = {
}, },
], ],
}; };
export const mockUserReportComment: { [_: string]: any } = {
weight: 'baik',
height: 'baik',
waist_size: 'baik',
changes_felt: 'baik',
hunger_level: 'baik',
fullness_level: 'baik',
heavy_meal: 'baik',
snacks: 'baik',
sweet_beverages: 'baik',
sugary_ingredients: 'baik',
fried_snacks: 'baik',
umami_snacks: 'baik',
sweet_snacks: 'baik',
fruits_portion: 'baik',
vegetables_portion: 'baik',
water_consumption: 'baik',
physical_activity: 'baik',
physical_activity_other: 'baik',
time_for_activity: 'baik',
feeling_rating: 'baik',
lesson_learned: 'baik',
problem_faced_and_feedbacks: 'baik',
};
export const validWeeklyReportCommentValues: { [_: string]: any } = {
weight: 'baik',
height: 'baik',
waist_size: 'baik',
changes_felt: 'baik',
hunger_level: 'baik',
fullness_level: 'baik',
heavy_meal: 'baik',
snacks: 'baik',
sweet_beverages: 'baik',
sugary_ingredients: 'baik',
fried_snacks: 'baik',
umami_snacks: 'baik',
sweet_snacks: 'baik',
fruits_portion: 'baik',
vegetables_portion: 'baik',
water_consumption: 'baik',
physical_activity: 'baik',
physical_activity_other: 'baik',
time_for_activity: 'baik',
feeling_rating: 'baik',
lesson_learned: 'baik',
problem_faced_and_feedbacks: 'baik',
};
...@@ -21,6 +21,7 @@ import { ...@@ -21,6 +21,7 @@ import {
import { setAuthHeader, resetAuthHeader } from 'services/api'; import { setAuthHeader, resetAuthHeader } from 'services/api';
import { iUserContext } from './types'; import { iUserContext } from './types';
import { TransactionStatus } from 'services/payment/models';
export const initialUser = { export const initialUser = {
id: null, id: null,
...@@ -88,27 +89,39 @@ export const useUserContext = (): iUserContext => { ...@@ -88,27 +89,39 @@ export const useUserContext = (): iUserContext => {
setUser(data.user); setUser(data.user);
}; };
const linkUserData = async (email: string) => { // For signup, link user to cart and diet profile
const linkUserData = async (data: LoginResponse) => {
const dietProfileId = await getCache(CACHE_KEYS.dietProfileId); const dietProfileId = await getCache(CACHE_KEYS.dietProfileId);
const cartId = await getCache(CACHE_KEYS.cartId); const cartId = await getCache(CACHE_KEYS.cartId);
if (dietProfileId && cartId) { if (dietProfileId && cartId) {
const response = await linkUserDataApi({ const response = await linkUserDataApi({
email, email: data.user.email,
diet_profile_id: parseInt(dietProfileId, 10), diet_profile_id: parseInt(dietProfileId, 10),
cart_id: parseInt(cartId, 10), cart_id: parseInt(cartId, 10),
}); });
return response; await authSuccess({
...data,
user: {
...data.user,
cart_id: response.data?.cart.id || null,
transaction_status: TransactionStatus.UNPAID,
},
});
} else {
await logout();
Toast.show({
type: 'error',
text1: 'Gagal registrasi akun',
text2: 'Terjadi kesalahan di sisi kami. Silakan coba lagi',
});
} }
return {
success: false,
};
}; };
const signup = async (registerData: RegistrationRequest) => { const signup = async (registerData: RegistrationRequest) => {
const response = await signupApi(registerData); const response = await signupApi(registerData);
if (response.success && response.data) { if (response.success && response.data) {
await linkUserData(response.data.user.email); await linkUserData(response.data);
await authSuccess(response.data);
} }
return response; return response;
}; };
...@@ -130,20 +143,8 @@ export const useUserContext = (): iUserContext => { ...@@ -130,20 +143,8 @@ export const useUserContext = (): iUserContext => {
access_token: tokens.accessToken, access_token: tokens.accessToken,
}); });
if (response.success && response.data) { if (response.success && response.data) {
// If signup, link user to cart and diet profile const authProcess = isLogin ? authSuccess : linkUserData;
if (!isLogin) { await authProcess(response.data);
const linkResponse = await linkUserData(response.data.user.email);
if (!linkResponse.success) {
await logout();
Toast.show({
type: 'error',
text1: 'Gagal registrasi dengan Google',
text2: 'Terjadi kesalahan di sisi kami. Silakan coba lagi',
});
}
}
await authSuccess(response.data);
} else { } else {
await logout(); await logout();
} }
......
...@@ -2,9 +2,12 @@ import React from 'react'; ...@@ -2,9 +2,12 @@ import React from 'react';
import { render, waitFor, fireEvent } from 'utils/testing'; import { render, waitFor, fireEvent } from 'utils/testing';
import DietReportForNutritionist from '.'; import DietReportForNutritionist from '.';
import { mockUserReportResponse } from 'mocks/userReport'; import {
import axios from 'axios'; mockUserReportResponse,
mockUserReportComment,
} from 'mocks/userReport';
import * as ROUTES from 'constants/routes'; import * as ROUTES from 'constants/routes';
import axios from 'axios';
jest.mock('react-native-toast-message'); jest.mock('react-native-toast-message');
jest.mock('axios'); jest.mock('axios');
...@@ -13,7 +16,26 @@ const mockAxios = axios as jest.Mocked<typeof axios>; ...@@ -13,7 +16,26 @@ const mockAxios = axios as jest.Mocked<typeof axios>;
describe('DietReportForNutritionist', () => { describe('DietReportForNutritionist', () => {
const data = mockUserReportResponse; const data = mockUserReportResponse;
it('renders and submits correctly when given valid comments', async () => { it('renders correctly when fetching previous comments', async () => {
mockAxios.request.mockImplementationOnce(() =>
Promise.resolve({
status: 200,
data: [mockUserReportComment],
}),
);
render(<DietReportForNutritionist />, ROUTES.clientDietReportNutritionist, {
routeParams: data,
});
await waitFor(() => expect(mockAxios.request).toBeCalled());
});
it('renders correctly when first creating valid comments', async () => {
mockAxios.request.mockImplementationOnce(() =>
Promise.resolve({
status: 200,
data: [],
}),
);
const { queryByText, getByText, getAllByPlaceholderText } = render( const { queryByText, getByText, getAllByPlaceholderText } = render(
<DietReportForNutritionist />, <DietReportForNutritionist />,
ROUTES.clientDietReportNutritionist, ROUTES.clientDietReportNutritionist,
...@@ -21,6 +43,7 @@ describe('DietReportForNutritionist', () => { ...@@ -21,6 +43,7 @@ describe('DietReportForNutritionist', () => {
routeParams: data, routeParams: data,
}, },
); );
await waitFor(() => expect(mockAxios.request).toBeCalled());
const textFieldsPage1 = getAllByPlaceholderText(/Tuliskan komentar.../i); const textFieldsPage1 = getAllByPlaceholderText(/Tuliskan komentar.../i);
textFieldsPage1.forEach((field) => { textFieldsPage1.forEach((field) => {
...@@ -57,10 +80,16 @@ describe('DietReportForNutritionist', () => { ...@@ -57,10 +80,16 @@ describe('DietReportForNutritionist', () => {
const submitButton = getByText('Selesai'); const submitButton = getByText('Selesai');
await waitFor(() => fireEvent.press(submitButton)); await waitFor(() => fireEvent.press(submitButton));
expect(queryByText(/Daftar Klien/i)).toBeTruthy(); expect(queryByText(/Riwayat Laporan Diet Klien/i)).toBeTruthy();
}); });
it('renders and does not redirect when api fails', async () => { it('renders correctly but does not redirect when api fails', async () => {
mockAxios.request.mockImplementationOnce(() =>
Promise.resolve({
status: 200,
data: [],
}),
);
const { queryByText, getByText, getAllByPlaceholderText } = render( const { queryByText, getByText, getAllByPlaceholderText } = render(
<DietReportForNutritionist />, <DietReportForNutritionist />,
ROUTES.clientDietReportNutritionist, ROUTES.clientDietReportNutritionist,
...@@ -68,6 +97,7 @@ describe('DietReportForNutritionist', () => { ...@@ -68,6 +97,7 @@ describe('DietReportForNutritionist', () => {
routeParams: data, routeParams: data,
}, },
); );
await waitFor(() => expect(mockAxios.request).toBeCalled());
const textFieldsPage1 = getAllByPlaceholderText(/Tuliskan komentar.../i); const textFieldsPage1 = getAllByPlaceholderText(/Tuliskan komentar.../i);
textFieldsPage1.forEach((field) => { textFieldsPage1.forEach((field) => {
...@@ -106,7 +136,7 @@ describe('DietReportForNutritionist', () => { ...@@ -106,7 +136,7 @@ describe('DietReportForNutritionist', () => {
const submitButton = getByText('Selesai'); const submitButton = getByText('Selesai');
await waitFor(() => fireEvent.press(submitButton)); await waitFor(() => fireEvent.press(submitButton));
expect(queryByText(/Daftar Klien/i)).toBeFalsy(); expect(queryByText(/Riwayat Laporan Diet Klien/i)).toBeFalsy();
}); });
afterAll(() => { afterAll(() => {
......
...@@ -2,16 +2,21 @@ import React, { FC, useState } from 'react'; ...@@ -2,16 +2,21 @@ import React, { FC, useState } from 'react';
import { View, StyleSheet } from 'react-native'; import { View, StyleSheet } from 'react-native';
import { layoutStyles } from 'styles'; import { layoutStyles } from 'styles';
import { pages } from './pages'; import { pages } from './pages';
import { WizardContainer, Toast } from 'components/core'; import { WizardContainer, Toast, Loader } from 'components/core';
import { useForm } from 'hooks'; import { useForm, useApi } from 'hooks';
import { dietReportCommentInitialValues, fieldValidations } from './schema'; import { dietReportCommentInitialValues, fieldValidations } from './schema';
import { generateValidationSchema } from 'utils/form'; import { generateValidationSchema } from 'utils/form';
import { DietReportPage } from './components'; import { DietReportPage } from './components';
import { useRoute, useNavigation } from '@react-navigation/native'; import { useRoute, useNavigation } from '@react-navigation/native';
import { createNutritionistCommentApi } from 'services/progress'; import {
createNutritionistCommentApi,
retrieveUserReportCommentByReportId,
updateNutritionistCommentApi,
} from 'services/progress';
import { import {
NutritionistCommentRequest, NutritionistCommentRequest,
UserReportResponse, UserReportResponse,
NutritionistComment,
} from 'services/progress/models'; } from 'services/progress/models';
import * as ROUTES from 'constants/routes'; import * as ROUTES from 'constants/routes';
import { import {
...@@ -19,17 +24,29 @@ import { ...@@ -19,17 +24,29 @@ import {
dietReportSelectFields, dietReportSelectFields,
} from 'constants/weeklyReport'; } from 'constants/weeklyReport';
interface ParamsDietReport {
id: number;
}
const DietReportForNutritionist: FC = () => { const DietReportForNutritionist: FC = () => {
const navigation = useNavigation(); const navigation = useNavigation();
const route = useRoute(); const route = useRoute();
const data = route.params as UserReportResponse; const userReport = route.params as UserReportResponse;
const [activeSlide, setActiveSlide] = useState(1); const [activeSlide, setActiveSlide] = useState(1);
const { isLoading, data: commentResponse } = useApi(() =>
retrieveUserReportCommentByReportId(userReport.id),
);
const getInitialValues = () => {
let defaultValues = { ...dietReportCommentInitialValues };
if (commentResponse && commentResponse.length > 0) {
const data = commentResponse[0];
Object.entries(data).map(([field, comment]) => {
const key = field as keyof NutritionistComment;
defaultValues[key] = comment;
});
}
return defaultValues;
};
const { const {
getTextInputProps, getTextInputProps,
handleSubmit, handleSubmit,
...@@ -37,21 +54,37 @@ const DietReportForNutritionist: FC = () => { ...@@ -37,21 +54,37 @@ const DietReportForNutritionist: FC = () => {
isFieldError, isFieldError,
values: formValues, values: formValues,
} = useForm({ } = useForm({
initialValues: dietReportCommentInitialValues, initialValues: getInitialValues(),
validationSchema: generateValidationSchema(fieldValidations), validationSchema: generateValidationSchema(fieldValidations),
enableReinitialize: true,
onSubmit: async (values) => { onSubmit: async (values) => {
const payload: NutritionistCommentRequest = { const payload: NutritionistCommentRequest = {
weekly_report: data.id, weekly_report: userReport.id,
...values, ...values,
}; };
const response = await createNutritionistCommentApi(payload); const commentId =
commentResponse && commentResponse.length > 0
? commentResponse[0].id
: null;
const response = commentId
? await updateNutritionistCommentApi(payload, commentId)
: await createNutritionistCommentApi(payload);
if (response.success) { if (response.success) {
Toast.show({ commentId
type: 'success', ? Toast.show({
text1: 'Sukses membuat komen', type: 'success',
text2: 'Komen anda terhadap laporan mingguan klien sudah tersimpan.', text1: 'Sukses mengubah komen',
}); text2:
navigation.navigate(ROUTES.clientListForNutritionist); 'Perubahan komen Anda terhadap laporan mingguan klien sudah tersimpan.',
})
: Toast.show({
type: 'success',
text1: 'Sukses membuat komen',
text2:
'Komen Anda terhadap laporan mingguan klien sudah tersimpan.',
});
navigation.navigate(ROUTES.weeklyReportChooseWeekForNutritionist);
} else { } else {
Toast.show({ Toast.show({
type: 'error', type: 'error',
...@@ -62,7 +95,9 @@ const DietReportForNutritionist: FC = () => { ...@@ -62,7 +95,9 @@ const DietReportForNutritionist: FC = () => {
}, },
}); });
const userReport = data; if (isLoading) {
return <Loader />;
}
const isCurrentPageError = (): boolean => { const isCurrentPageError = (): boolean => {
if (activeSlide === 1) { if (activeSlide === 1) {
......
...@@ -48,8 +48,7 @@ const statusBeratBadan = (response: DietProfileResponse): ResultPageContent => { ...@@ -48,8 +48,7 @@ const statusBeratBadan = (response: DietProfileResponse): ResultPageContent => {
}, },
}, },
{ {
header: header: `Untuk orang dengan tinggi ${height} cm, berikut rentang berat badan yang ideal:`,
'Untuk orang dengan tinggi 156 cm, berikut rentang berat badan yang ideal:',
content: { content: {
statistics: [ statistics: [
[ [
......
...@@ -12,11 +12,13 @@ const physicalActivity = [ ...@@ -12,11 +12,13 @@ const physicalActivity = [
]; ];
export const getPhysicalActivity = (activity: number[]) => { export const getPhysicalActivity = (activity: number[]) => {
return activity.map((act, i) => let res = '';
activity.map((act, i) =>
i === activity.length - 1 i === activity.length - 1
? `- ${physicalActivity[act - 1]}` ? res.concat(`- ${physicalActivity[act - 1]}`)
: `- ${physicalActivity[act - 1]}\n`, : res.concat(`- ${physicalActivity[act - 1]}\n`),
); );
return res;
}; };
const timeForActivity = [ const timeForActivity = [
......
...@@ -20,23 +20,27 @@ const ReadOnlyWeeklyReport: FC = () => { ...@@ -20,23 +20,27 @@ const ReadOnlyWeeklyReport: FC = () => {
retrieveUserReportCommentByReportId(data.id), retrieveUserReportCommentByReportId(data.id),
); );
const pageComponents = [
<WeeklyReportPage questions={page1(data)} title="Laporan Diet Anda" />,
<WeeklyReportPage questions={page2(data)} />,
<WeeklyReportPage
questions={page3(data)}
title="Selama 1 minggu terakhir, berapa rata-rata Anda mengonsumsi jenis makanan dibawah ini selama seharian?"
/>,
<WeeklyReportPage questions={page4(data)} />,
];
if (isLoading) { if (isLoading) {
return <Loader />; return <Loader />;
} }
if (commentData.length === 0) { if (!commentData || commentData.length === 0) {
return <EmptyDataPage text="Belum ada komentar dari nutrisionis" />; return <EmptyDataPage text="Belum ada komentar dari nutrisionis" />;
} }
const firstCommentData = commentData[0];
const pageComponents = [
<WeeklyReportPage
questions={page1(data, firstCommentData)}
title="Laporan Diet Anda"
/>,
<WeeklyReportPage questions={page2(data, firstCommentData)} />,
<WeeklyReportPage
questions={page3(data, firstCommentData)}
title="Selama 1 minggu terakhir, berapa rata-rata Anda mengonsumsi jenis makanan dibawah ini selama seharian?"
/>,
<WeeklyReportPage questions={page4(data, firstCommentData)} />,
];
return ( return (
<> <>
......
import { UserReportResponse } from 'services/progress/models'; import {
UserReportResponse,
NutritionistCommentResponse,
} from 'services/progress/models';
import { QuestionComment } from '../types'; import { QuestionComment } from '../types';
export const page1 = (reportData: UserReportResponse): QuestionComment[] => [ export const page1 = (
reportData: UserReportResponse,
commentData: NutritionistCommentResponse,
): QuestionComment[] => [
{ {
questions: [ questions: [
{ {
...@@ -9,7 +15,7 @@ export const page1 = (reportData: UserReportResponse): QuestionComment[] => [ ...@@ -9,7 +15,7 @@ export const page1 = (reportData: UserReportResponse): QuestionComment[] => [
answer: `${reportData.weight}`, answer: `${reportData.weight}`,
}, },
], ],
comment: 'keren bingits', comment: commentData.weight,
}, },
{ {
questions: [ questions: [
...@@ -18,7 +24,7 @@ export const page1 = (reportData: UserReportResponse): QuestionComment[] => [ ...@@ -18,7 +24,7 @@ export const page1 = (reportData: UserReportResponse): QuestionComment[] => [
answer: `${reportData.height}`, answer: `${reportData.height}`,
}, },
], ],
comment: 'pertahankan\nnak', comment: commentData.height,
}, },
{ {
questions: [