Fakultas Ilmu Komputer UI

Commit 576f964e authored by Wulan Mantiri's avatar Wulan Mantiri
Browse files

Merge branch 'PBI-13-client_report_integration_and_nav'' into 'staging'

Integrate client weekly report API and implement bottom navbar

See merge request !66
parents f7e12547 b6c6e618
Pipeline #80670 passed with stages
in 57 minutes and 32 seconds
......@@ -4,8 +4,6 @@ import { render } from '@testing-library/react-native';
import { ErrorToast } from './styles';
import App from '.';
jest.useFakeTimers();
describe('Application', () => {
it('renders correctly', () => {
render(<App />);
......
......@@ -5,12 +5,11 @@ import { ThemeProvider } from 'react-native-elements';
import Toast from 'react-native-toast-message';
import dayjs from 'dayjs';
import { DietelaCoverLoader } from 'components/core';
import { DietelaCoverLoader, LogoutButton } from 'components/core';
import ContextProvider, { UserContext } from 'provider';
import { theme } from 'styles/theme';
import { screenOptions, toastConfig } from './styles';
import LogoutButton from './LogoutButton';
import { getNavigation } from './schema';
dayjs.locale('id');
......
import * as ROUTES from 'constants/routes';
import {
publicNavigation,
clientNavigation,
nutritionistNavigation,
adminNavigation,
onboardingClientNavigation,
unpaidClientNavigation,
paidClientNavigation,
} from 'constants/navigation';
import { UserRole, AuthUserResponse } from 'services/auth/models';
import { TransactionStatus } from 'services/payment/models';
......@@ -21,16 +20,12 @@ export const getNavigation = (
initialRoute: ROUTES.checkout,
navigation: unpaidClientNavigation,
};
} else if (!user.is_finished_onboarding) {
} else {
return {
initialRoute: ROUTES.extendedQuestionnaire,
navigation: onboardingClientNavigation,
initialRoute: ROUTES.clientTabNavigation,
navigation: paidClientNavigation,
};
}
return {
initialRoute: ROUTES.clientProfile,
navigation: clientNavigation,
};
}
if (user.role === UserRole.NUTRITIONIST) {
......
......@@ -6,8 +6,10 @@ import LogoutButton from '.';
describe('LogoutButton', () => {
const userContextMock = {
user: {
id: 1,
},
isAuthenticated: true,
isUnpaidClient: true,
logout: jest.fn(),
};
......
......@@ -28,7 +28,6 @@ export const styles = StyleSheet.create({
justifyContent: 'space-between',
backgroundColor: colors.primaryYellow,
borderRadius: 20,
paddingRight: 10,
},
finishButton: {
borderRadius: 30,
......
......@@ -4,6 +4,7 @@ export { default as CarouselPagination } from './CarouselPagination';
export { default as InfoCard } from './InfoCard';
export { default as Link } from './Link';
export { default as Loader, DietelaCoverLoader } from './Loader';
export { default as LogoutButton } from './LogoutButton';
export { default as ResultCard } from './ResultCard';
export { default as Statistic } from './Statistic';
export { default as Toast } from 'react-native-toast-message';
......
......@@ -46,6 +46,7 @@ const LikertScale: FC<Props> = ({
style={{ flex: 1 / choices.length }}
key={`button${choice.value}`}>
<RadioButton
testID={`${label}-${value}`}
key={choice.value}
checked={value === choice.value}
onPress={() => onChange(choice.value)}
......
......@@ -22,7 +22,6 @@ describe('StepByStepForm component', () => {
route: name,
})),
currentPage: 0,
finishRedirectRoute: 'finishroute',
};
it('only has "Isi" button when form is never filled', () => {
......@@ -50,7 +49,11 @@ describe('StepByStepForm component', () => {
it('redirects to finish route when button "Selesai" is pressed', () => {
const { getByText } = render(
<StepByStepForm {...props} currentPage={pages.length} />,
<StepByStepForm
{...props}
currentPage={pages.length}
finishRedirectRoute="finish"
/>,
);
const finishButton = getByText(/Selesai/i);
expect(finishButton).toBeTruthy();
......
......@@ -60,20 +60,22 @@ const StepByStepForm: FC<Props> = ({
</View>
))}
</View>
<View style={styles.bottomContainer}>
<Button
title="Selesai"
onPress={() =>
navigation.reset({
index: 0,
routes: [{ name: finishRedirectRoute }],
})
}
buttonStyle={styles.finishButton}
titleStyle={styles.buttonTitle}
disabled={currentPage !== pages.length}
/>
</View>
{finishRedirectRoute ? (
<View style={styles.bottomContainer}>
<Button
title="Selesai"
onPress={() =>
navigation.reset({
index: 0,
routes: [{ name: finishRedirectRoute }],
})
}
buttonStyle={styles.finishButton}
titleStyle={styles.buttonTitle}
disabled={currentPage !== pages.length}
/>
</View>
) : null}
</ScrollView>
);
};
......
......@@ -5,5 +5,5 @@ export interface Props {
}[];
currentPage: number;
defaultValues?: any;
finishRedirectRoute: string;
finishRedirectRoute?: string;
}
import * as ROUTES from 'constants/routes';
import {
// Extended Questionnaire
ExtendedQuestionnaire,
ConsentForm,
Questionnaire1,
Questionnaire2,
Questionnaire3,
Questionnaire4,
Questionnaire5,
// Public
AllAccessQuestionnaire,
ChoosePlan,
......@@ -26,13 +17,12 @@ import {
ReadOnlyDietProfile,
ClientListNutritionist,
ComingSoonPage,
ClientProfile,
ClientDietRecommendationForAdmin,
PaymentWebView,
ProfileDietRecommendation,
ClientListAdmin,
LoginChoosePlan,
WeeklyReport,
ClientNavigation,
} from 'scenes';
import { FC } from 'react';
import DietReportForNutritionist from 'scenes/nutritionist/DietReportForNutritionist';
......@@ -99,52 +89,6 @@ export const publicNavigation: NavRoute[] = [
},
];
const defaultClientNavigation: NavRoute[] = [
{
name: ROUTES.extendedQuestionnaire,
component: ExtendedQuestionnaire,
header: 'Profil Saya',
},
...[
{
component: ConsentForm,
header: 'Persetujuan',
},
{
component: Questionnaire1,
header: 'Identitas Diri',
},
{
component: Questionnaire2,
header: 'Pola Makan',
},
{
component: Questionnaire3,
header: 'Konsumsi Makan',
},
{
component: Questionnaire4,
header: 'Gaya Hidup',
},
{
component: Questionnaire5,
header: 'Kondisi Pribadi',
},
].map((nav, id) => ({
...nav,
name: ROUTES.extendedQuestionnaireById(id),
})),
];
export const onboardingClientNavigation: NavRoute[] = [
...defaultClientNavigation,
{
name: ROUTES.clientProfile,
component: ClientProfile,
header: 'Profil Saya',
},
];
export const unpaidClientNavigation: NavRoute[] = [
{
name: ROUTES.checkout,
......@@ -162,25 +106,18 @@ export const unpaidClientNavigation: NavRoute[] = [
},
...navigation,
{
name: ROUTES.clientProfile,
component: ClientProfile,
header: 'Profil Saya',
name: ROUTES.clientTabNavigation,
component: ClientNavigation,
header: 'Dietela',
},
...defaultClientNavigation,
];
export const clientNavigation: NavRoute[] = [
{
name: ROUTES.clientProfile,
component: ClientProfile,
header: 'Profil Saya',
},
export const paidClientNavigation: NavRoute[] = [
{
name: ROUTES.clientWeeklyReport,
component: WeeklyReport,
header: 'Laporan Diet Saya',
name: ROUTES.clientTabNavigation,
component: ClientNavigation,
header: 'Dietela',
},
...defaultClientNavigation,
];
export const nutritionistNavigation: NavRoute[] = [
......@@ -239,6 +176,7 @@ export const adminNavigation: NavRoute[] = [
},
];
// FOR TESTING PURPOSES
export const testNavigation: NavRoute[] = [
...unpaidClientNavigation,
...nutritionistNavigation,
......
......@@ -23,8 +23,11 @@ export const payment = 'payment';
export const paymentResult = `${payment}/result`;
const client = 'client';
export const clientTabNavigation = `${client}/tab-navigation`;
export const clientProfile = `${client}/profile`;
export const clientRecommendation = `${client}/recommendation`;
export const clientWeeklyReport = `${client}/report`;
export const clientChat = `${client}/chat`;
const nutritionist = 'nutritionist';
export const clientListForNutritionist = `${nutritionist}/client-list`;
......@@ -41,5 +44,6 @@ 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`;
const weeklyReport = 'weekly-report';
export const weeklyReportForm = `${weeklyReport}/form`;
export const weeklyReportChooseWeek = `${weeklyReport}/choose-week`;
......@@ -10,21 +10,21 @@ export const dietReportTextFields: { [_: string]: TextFieldSchema[] } = {
dietReportPage1: [
{
label: 'Berat Badan (kg)',
placeholder: 'Masukkan yang terakhir diukur',
placeholder: 'Masukkan berat badan yang terakhir diukur',
name: 'weight',
required: true,
keyboardType: 'numeric',
},
{
label: 'Tinggi Badan (cm)',
placeholder: 'Masukkan yang terakhir diukur',
placeholder: 'Masukkan tinggi badan yang terakhir diukur',
name: 'height',
required: true,
keyboardType: 'numeric',
},
{
label: 'Lingkar Pinggang (cm)',
placeholder: 'Masukkan yang terakhir diukur',
placeholder: 'Masukkan lingkar pinggang yang terakhir diukur',
name: 'waist_size',
required: true,
keyboardType: 'numeric',
......@@ -45,6 +45,7 @@ export const dietReportTextFields: { [_: string]: TextFieldSchema[] } = {
{
label:
'Dalam 1 minggu terakhir, apa saja yang sudah bisa Anda pelajari dari program ini?',
placeholder: 'Masukkan pelajaran minggu ini',
name: 'lesson_learned',
required: true,
multiline: true,
......@@ -54,6 +55,7 @@ export const dietReportTextFields: { [_: string]: TextFieldSchema[] } = {
label: `Silahkan sampaikan disini, jika Anda mempunyai:
(1) kendala, keluhan atau kesulitan dalam mengikuti program, atau
(2) masukan, saran, dan komplain terkait layanan sejauh ini.`,
placeholder: 'Masukkan keluh kesah atau saran bagi Dietela',
name: 'problem_faced_and_feedbacks',
multiline: true,
},
......
......@@ -8,7 +8,7 @@ const useDownloadFiles = (
fileType: FileType,
fileTitle?: string,
) => {
const fileName = fileTitle ? fileTitle : url.split('/').pop();
const fileName = fileTitle ? fileTitle : url?.split('/').pop();
const extension = fileName?.split('.').pop()?.toUpperCase() || '-';
const askWritePermission = async () =>
......
import { createContext, useCallback, useState } from 'react';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import { Toast } from 'components/core';
import Toast from 'react-native-toast-message';
import CACHE_KEYS from 'constants/cacheKeys';
import { removeCache, getCache, setCache } from 'utils/cache';
......@@ -37,6 +37,7 @@ export const UserContext = createContext<iUserContext>({
isAuthenticated: false,
isLoading: false,
isFirstLoading: false,
setUser: (_: AuthUserResponse) => {},
getUser: () => Promise.reject(),
signup: () => Promise.reject(),
login: () => Promise.reject(),
......@@ -155,6 +156,7 @@ export const useUserContext = (): iUserContext => {
isAuthenticated: user.id !== null,
isLoading,
isFirstLoading,
setUser,
getUser,
signup,
login,
......
......@@ -12,6 +12,7 @@ export interface iUserContext {
isAuthenticated: boolean;
isLoading: boolean;
isFirstLoading: boolean;
setUser: (_: AuthUserResponse) => void;
getUser: () => Promise<void>;
signup: (
data: RegistrationRequest,
......
......@@ -22,7 +22,7 @@ const Checkout: FC = () => {
const { isLoading, data } = useApi(() => retrieveCartApi(user.cart_id));
const pay = async () => {
const response = await payWithMidtransApi(data?.id);
const response = await payWithMidtransApi(user.cart_id);
if (response.success && response.data) {
navigation.navigate(ROUTES.payment, { url: response.data.redirect_url });
} else {
......
......@@ -22,7 +22,7 @@ import { UserContext } from 'provider';
const ChoosePlan: FC = () => {
const navigation = useNavigation();
const { user, isAuthenticated } = useContext(UserContext);
const { user, setUser, isAuthenticated } = useContext(UserContext);
const [currentPage, setCurrentPage] = useState(1);
const [isSubmitting, setIsSubmitting] = useState(false);
......@@ -41,8 +41,13 @@ const ChoosePlan: FC = () => {
setIsSubmitting(false);
if (response.success) {
await setCache(CACHE_KEYS.cartId, response.data?.id);
const cartId = response.data?.id as number;
await setCache(CACHE_KEYS.cartId, cartId);
if (isAuthenticated) {
setUser({
...user,
cart_id: cartId,
});
navigation.navigate(ROUTES.checkout);
} else {
navigation.reset({
......
Markdown is supported
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