Fakultas Ilmu Komputer UI

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

Merge branch 'pbi-14-progress-diet-nutritionist' into 'staging'

Static Diet Report For Nutritionist

See merge request !65
parents 2e0af7c3 3f700cb4
Pipeline #80621 passed with stages
in 11 minutes and 47 seconds
import {
UserReportRequest,
UserReportResponse,
} from 'services/progress/models';
import { UserRole } from 'services/auth/models';
export const mockUserReportRequest: UserReportRequest = {
weight: 155,
height: 188,
waist_size: 100,
changes_felt: 1,
hunger_level: 1,
fullness_level: 1,
heavy_meal: 1,
snacks: 1,
sweet_beverages: 1,
sugary_ingredients: 1,
fried_snacks: 1,
umami_snacks: 1,
sweet_snacks: 1,
fruits_portion: 1,
vegetables_portion: 1,
water_consumption: 1,
physical_activity: [1, 2],
physical_activity_other: 'n',
time_for_activity: 1,
feeling_rating: 2,
lesson_learned: 'a',
problem_faced_and_feedbacks: 'x',
};
export const mockUserReportResponse: UserReportResponse = {
id: 4,
nutritionist: 1,
week_num: 2,
client: {
id: 9,
name: 'Shin Ryujin',
email: 'ryujin@itzy.com',
role: UserRole.CLIENT,
},
...mockUserReportRequest,
};
......@@ -7,7 +7,9 @@ import { useNavigation } from '@react-navigation/native';
import { retrieveClientListApi } from 'services/profiles';
import { useApi, useDownloadFiles } from 'hooks';
import { getAbsoluteUrl } from 'utils/format';
import { BigButton, Loader, EmptyDataPage } from 'components/core';
import BigButton from '../../core/BigButton';
import Loader from '../../core/Loader';
import EmptyDataPage from '../../core/EmptyDataPage';
import { Section } from 'components/layout';
import { UserRole } from 'services/auth/models';
import { FileType } from 'hooks/useDownloadFiles/schema';
......
......@@ -35,6 +35,7 @@ import {
WeeklyReport,
} from 'scenes';
import { FC } from 'react';
import DietReportForNutritionist from 'scenes/nutritionist/DietReportForNutritionist';
export interface NavRoute<T = any> {
name: string;
......@@ -195,7 +196,7 @@ export const nutritionistNavigation: NavRoute[] = [
},
{
name: ROUTES.clientDietReportNutritionist,
component: ComingSoonPage,
component: DietReportForNutritionist,
header: 'Laporan Klien',
},
{
......
......@@ -40,3 +40,6 @@ export const clientProfileAdmin = `${admin}/client-profile`;
export const clientDietReportAdmin = `${admin}/client-diet-report`;
export const clientChatAdmin = `${admin}/client-chat`;
export const clientDietRecommendation = `${admin}/client-diet-recommendation`;
const progress = 'progress';
export const userReport = `${progress}/user-report`;
import { TextFieldSchema } from 'types/form';
import {
likertScale5,
likertScale10,
averageConsumptionOptions,
} from './options';
export const dietReportTextFields: { [_: string]: TextFieldSchema[] } = {
dietReportPage1: [
{
label: 'Berat Badan (kg)',
placeholder: 'Masukan berat badan yang terakhir anda ukur',
name: 'weight',
required: true,
keyboardType: 'numeric',
},
{
label: 'Tinggi Badan (cm)',
placeholder: 'Masukan tinggi badan yang terakhir anda ukur',
name: 'height',
required: true,
keyboardType: 'numeric',
},
{
label: 'Lingkar Pinggang (cm)',
placeholder: 'Masukan lingkar pinggang yang terakhir anda ukur',
name: 'waist_size',
keyboardType: 'numeric',
},
],
dietReportPage2: [
{
label:
'Selama 1 minggu terakhir, berapa rata-rata total gelas air putih yang Anda minum?',
placeholder: 'Masukan dalam angka',
name: 'water_consumption',
keyboardType: 'numeric',
},
],
dietReportPage4: [
{
label:
'Dalam 1 minggu terakhir, Apa saja yang sudah bisa Anda pelajari dari program ini?',
placeholder: 'Masukan pelajarann-pelajaran yang anda dapatkan',
name: 'lesson_learned',
},
{
label:
'Silahkan sampaikan disini, jika Anda mempunyai kendala atau keluhan atau kesulitan dalam mengikuti program.',
placeholder:
'Jika ada masukan, saran, dan komplain terkait layanan sejauh ini, juga bisa disampaikan disini',
name: 'problem_faced_and_feedbacks',
},
],
};
export const dietReportSelectFields: { [_: string]: any[] } = {
dietReportPage2: [
{
label: 'Apakah sudah mulai terasa ada perubahan ukuran baju atau celana?',
placeholder: 'Skor 3: jika tidak ada perubahan sama sekali',
name: 'changes_felt',
lowestScoreDescription: '1: Belum terasa sama sekali',
highestScoreDescription: '5: Sudah sangat berubah',
choiceList: likertScale5,
},
{
label:
'Secara rata-rata, sebelum waktu makan selama 1 minggu terakhir ini, dimana level rasa lapar yang Anda rasakan?',
placeholder: 'Cek indikator di program book kamu ya',
name: 'hunger_level',
lowestScoreDescription: '1: Sangat kelaparan',
highestScoreDescription: '10: Sangat begah (kenyang berlebihan)',
choiceList: likertScale10,
},
{
label:
'Secara rata-rata, saat berhenti makan selama 1 minggu terakhir ini, dimana level rasa kenyang yang Anda rasakan?',
placeholder: 'Cek indikator di program book kamu ya',
name: 'fullness_level',
lowestScoreDescription: '1: Sangat kelaparan',
highestScoreDescription: '10: Sangat begah (kenyang berlebihan)',
choiceList: likertScale10,
},
{
label:
'Selama 1 minggu terakhir, secara rata-rata, berapa kali Anda makan berat atau makan utama dalam 1 hari?',
placeholder: 'Pilih salah satu',
name: 'heavy_meal',
choiceList: ['1x/hari', '2x/hari', '3x/hari', 'Lebih dari 3x/hari'],
},
{
label:
'Selama 1 minggu terakhir, secara rata-rata, berapa kali Anda makan cemilan dalam 1 hari?',
placeholder: 'Pilih salah satu',
name: 'snacks',
choiceList: [
'1x/hari',
'2x/hari',
'3x/hari',
'Lebih dari 3x/hari',
'Hampir tidak ada',
],
},
],
dietReportPage3: [
{
label: 'Minuman manis (satuan: gelas)',
placeholder: 'Pilih salah satu',
name: 'sweet_beverages',
choiceList: averageConsumptionOptions,
},
{
label:
'Gula pasir, gula aren, sirup, selai, atau madu (satuan: sendok makan)',
placeholder: 'Pilih salah satu',
name: 'sugary_ingredients',
choiceList: averageConsumptionOptions,
},
{
label: 'Cemilan digoreng (satuan: potong)',
placeholder: 'Pilih salah satu',
name: 'fried_snacks',
choiceList: averageConsumptionOptions,
},
{
label:
'Makanan ringan asin atau gurih (seperti makanan ringan kemasan, ciki-cikian, keripik) (satuan: bungkus)',
placeholder: 'Pilih salah satu',
name: 'umami_snacks',
choiceList: averageConsumptionOptions,
},
{
label:
'Cemilan manis (seperti kue-kue manis, brownis, cake, biskuit, cokelat, wafer) (satuan: potong)',
placeholder: 'Pilih salah satu',
name: 'sweet_snacks',
choiceList: averageConsumptionOptions,
},
{
label: 'Porsi buah',
placeholder: 'Pilih salah satu',
name: 'fruits_portion',
choiceList: averageConsumptionOptions,
},
{
label: 'Porsi sayur',
placeholder: 'Pilih salah satu',
name: 'vegetables_portion',
choiceList: averageConsumptionOptions,
},
],
dietReportPage4: [
{
label:
'Selama 1 minggu terakhir, pilih semua jenis aktivitas atau olahraga yang sudah Anda lakukan',
placeholder: 'Dapat memilih lebih dari satu',
name: 'physical_activity',
hasOtherChoice: true,
otherChoiceValue: 9,
choiceList: [
'Hampir tidak pernah olahraga',
'Jogging',
'Senam aerobic, zumba, yoga, dan sejenisnya',
'Sepak bola atau futsal',
'Renang',
'Basket',
'Bulu tangkis',
'Voli',
],
},
{
label:
'Selama 1 minggu (7 hari) terakhir, berapa total menit yang Anda habiskan untuk melakukan bergerak aktif dan olahraga di atas dalam seminggu?',
placeholder: 'Pilih salah satu',
name: 'time_for_activity',
choiceList: [
'0 - 60 menit',
'60 - 100 menit',
'100 - 120 menit',
'120 - 150 menit',
'150 - 175 menit',
'175 - 200 menit',
'200 - 250 menit',
'Lebih dari 250 menit',
],
},
{
label: 'Sejauh ini, bagaimana perasaan Anda dalam mengikuti program?',
placeholder: 'Pilih salah satu',
name: 'feeling_rating',
choiceList: [
'Bintang 1: Rasanya mau menyerah saja',
'Bintang 2: Capek, susah, bosen, males, repot, sibuk',
'Bintang 3: Biasa aja, meski ada kendala tapi semua bisa diatur',
'Bintang 4: Lancar terus, semangat cukup stabil, gak ada masalah',
'Bintang 5: Super seneng, semangat banget, worry-free lah',
],
},
],
};
......@@ -123,7 +123,7 @@ export const dietReportSelectFields: { [_: string]: any[] } = {
{
label: 'Gula pasir, gula aren, sirup, selai, atau madu',
helperText: 'dalam satuan sendok makan',
name: 'sugar',
name: 'sugary_ingredients',
choiceList: averageConsumptionOptions,
},
{
......
import { FC } from 'react';
import { ScrollView, View } from 'react-native';
import { styles } from './styles';
import React from 'react';
import { DietReportPageContent, dietReportPage } from '../../pages/types';
import QuestionAnswerCommentCard from '../QuestionAnswerCommentCard';
import { Text } from 'react-native-elements';
import { typographyStyles } from 'styles';
import { TextInput } from 'react-native-gesture-handler';
const DietReportPage: FC<{
content: DietReportPageContent;
pageName: string;
getTextInputProps?: any;
}> = ({ content, pageName, getTextInputProps }) => {
return (
<ScrollView style={styles.container}>
{pageName === dietReportPage.PAGE3 ? (
<Text style={[typographyStyles.headingMedium, styles.bottomMargin]}>
Selama 1 minggu terakhir, berapa rata-rata Anda mengonsumsi jenis
makanan dibawah ini selama seharian
</Text>
) : null}
{content.questions.map((qst, idx) => {
if (pageName === dietReportPage.PAGE3) {
return (
<QuestionAnswerCommentCard
key={idx}
pageName={pageName}
questionName={qst.questionName}
question={qst.label}
answer={qst.answer}
/>
);
}
return (
<QuestionAnswerCommentCard
key={idx}
pageName={pageName}
questionName={qst.questionName}
question={qst.label}
answer={qst.answer}
textInputProps={qst.textInputProps}
lowestScoreDescription={qst.lowestScoreDescription}
highestScoreDescription={qst.highestScoreDescription}
/>
);
})}
{pageName === dietReportPage.PAGE3 ? (
<View>
<Text style={[typographyStyles.bodySmall, styles.topMargin]}>
Komentar:
</Text>
<TextInput
style={[typographyStyles.bodySmall, styles.input]}
{...getTextInputProps('average_consumption')}
placeholder="Tuliskan komentar..."
multiline={true}
/>
</View>
) : null}
</ScrollView>
);
};
export default DietReportPage;
import { StyleSheet, Dimensions } from 'react-native';
export const styles = StyleSheet.create({
nameMargin: {
marginBottom: 15,
},
container: {
width: Dimensions.get('window').width - 40,
},
input: {
borderWidth: 1,
height: 90,
borderRadius: 4,
marginBottom: 20,
padding: 15,
marginTop: 5,
},
bottomMargin: {
marginBottom: 10,
},
topMargin: {
marginTop: 10,
},
});
import { FC } from 'react';
import { View, Text, TextInput } from 'react-native';
import React from 'react';
import { InfoCard } from 'components/core';
import { typographyStyles } from 'styles';
import { styles } from './styles';
import { dietReportPage } from '../../pages/types';
interface Props {
question: string;
pageName: string;
answer: string;
textInputProps?: any;
questionName?: string | null;
lowestScoreDescription?: string | null;
highestScoreDescription?: string | null;
testID?: string;
}
const QuestionAnswerCommentCard: FC<Props> = ({
questionName,
pageName,
question,
answer,
textInputProps,
lowestScoreDescription,
highestScoreDescription,
testID,
}) => {
return (
<View style={styles.labelContainer} testID={testID}>
{questionName === 'name' ? (
<Text style={[typographyStyles.headingLarge, styles.nameSize]}>
{answer}
</Text>
) : null}
{questionName === 'email' ? (
<Text style={[typographyStyles.bodySmall, styles.bottomMargin]}>
{answer}
</Text>
) : null}
{pageName === dietReportPage.PAGE3 ? (
<View>
<Text
style={[
typographyStyles.bodyLarge,
styles.topMargin,
styles.smallBottomMargin,
]}>
{question}
</Text>
<InfoCard content={answer} />
{/* <Text style={[typographyStyles.bodyMedium, styles.label]}>
{answer}
</Text> */}
</View>
) : null}
{pageName !== dietReportPage.PAGE3 &&
questionName !== 'name' &&
questionName !== 'email' ? (
<View>
<Text style={[typographyStyles.bodyLarge]}>{question}</Text>
{lowestScoreDescription ? (
<Text style={[typographyStyles.bodySmall, styles.descMargin]}>
{lowestScoreDescription}
</Text>
) : null}
{highestScoreDescription ? (
<Text
style={[typographyStyles.bodySmall, styles.smallBottomMargin]}>
{highestScoreDescription}
</Text>
) : null}
{/* <Text style={[typographyStyles.bodyMedium, styles.label]}>
{answer}
</Text> */}
<InfoCard content={answer} />
<Text style={[typographyStyles.bodySmall, styles.topMargin]}>
Komentar:
</Text>
<TextInput
style={[typographyStyles.bodySmall, styles.input]}
{...textInputProps}
placeholder="Tuliskan komentar..."
multiline={true}
/>
</View>
) : null}
</View>
);
};
export default QuestionAnswerCommentCard;
import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
label: {
marginBottom: 5,
marginTop: 5,
},
labelContainer: {
alignSelf: 'stretch',
},
answer: {
flexDirection: 'row',
},
nameSize: {
fontSize: 32,
},
bottomMargin: {
marginBottom: 20,
},
smallBottomMargin: {
marginBottom: 10,
},
topMargin: {
marginTop: 10,
},
descMargin: {
marginTop: 5,
},
input: {
borderRadius: 4,
borderWidth: 1,
height: 90,
marginTop: 5,
marginBottom: 20,
padding: 15,
},
});
export { default as DietReportPage } from './DietReportPage';
export { default as QuestionAnswerCommentCard } from './QuestionAnswerCommentCard';
import React from 'react';
import { render } from 'utils/testing';
import * as ROUTES from 'constants/routes';
import DietReportForNutritionist from '.';
jest.useFakeTimers();
describe('DietReportForNutritionist', () => {
it('renders correctly', () => {
render(<DietReportForNutritionist />, ROUTES.userReport);
});
});
import React, { FC, useState } from 'react';
import { View, StyleSheet, Dimensions } from 'react-native';
import { layoutStyles } from 'styles';
import Carousel from 'react-native-snap-carousel';
import { pages } from './pages';
import { CarouselPagination, BigButton } from 'components/core';
import { useForm } from 'hooks';
import { dietReportCommentInitialValues, fieldValidations } from './schema';
import { generateValidationSchema } from 'utils/form';
import { DietReportPage } from './components';
import { mockUserReportResponse } from '__mocks__/userReport';
const DietReportForNutritionist: FC = () => {
const [activeSlide, setActiveSlide] = useState(0);
const userReport = mockUserReportResponse;
const { getTextInputProps, handleSubmit, isSubmitting } = useForm({
initialValues: dietReportCommentInitialValues,
validationSchema: generateValidationSchema(fieldValidations),
onSubmit: async (values) => {
console.log('masuk');
console.log(values);
},
});
return (
<View style={[layoutStyles, styles.reportContainer]}>
<View style={[styles.flexContainer]}>
<Carousel
data={pages.map((page, idx) => (
<DietReportPage
key={idx}
content={page.pageContent(userReport, getTextInputProps)}
pageName={page.pageName}
getTextInputProps={getTextInputProps}