Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Com 3851 #692

Merged
merged 14 commits into from
Dec 13, 2024
39 changes: 39 additions & 0 deletions src/components/AttendanceEndScreen/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { View, Text, Image } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { MaterialIcons } from '@expo/vector-icons';
import styles from './styles';
manonpalin marked this conversation as resolved.
Show resolved Hide resolved
import NiPrimaryButton from '../form/PrimaryButton';
import { PINK } from '../../styles/colors';

interface AttendanceEndScreenProps {
traineeName: string,
failUpload: boolean,
goToNextScreen: () => void,
}

const AttendanceEndScreen = ({ traineeName, failUpload, goToNextScreen }: AttendanceEndScreenProps) => (
<SafeAreaView style={styles.safeArea} edges={['top']}>
{failUpload
? <View style={styles.errorContainer}>
<Text style={styles.title}>Echec de l&apos;envoi de la demande à {traineeName}</Text>
<MaterialIcons style={styles.icon} size={200} name={'warning'} color={PINK[500]} />
<Text style={styles.text}>Veuillez réitérer votre demande</Text>
</View>
: <>
<View style={styles.upperContainer}>
<Text style={styles.title}>Une demande d&apos;émargement a été envoyée à {traineeName}</Text>
</View>
<Text style={styles.text}>Elle est disponible sur la page de la formation sur son application mobile</Text>
<Image source={require('../../../assets/images/aux_fierte.webp')} style={styles.image} />
<Text style={styles.text}>
N&apos;oubliez pas de reporter les émargements dans le tableau prévu à cet effet sur l&apos;application
web Compani
</Text>
</>
}
<View style={styles.footer}>
<NiPrimaryButton caption={'Terminer'} onPress={goToNextScreen} />
</View>
</SafeAreaView>
);
export default AttendanceEndScreen;
48 changes: 48 additions & 0 deletions src/components/AttendanceEndScreen/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { StyleSheet } from 'react-native';
import { MARGIN } from '../../styles/metrics';
import { FIRA_SANS_BLACK, FIRA_SANS_BOLD } from '../../styles/fonts';
import { PINK } from '../../styles/colors';

const styles = StyleSheet.create({
safeArea: {
flexGrow: 1,
marginHorizontal: MARGIN.LG,
justifyContent: 'space-between',
},
title: {
...FIRA_SANS_BOLD.LG,
marginVertical: MARGIN.LG,
textAlign: 'center',
},
text: {
...FIRA_SANS_BLACK.MD,
color: PINK[600],
marginVertical: MARGIN.LG,
textAlign: 'center',
},
footer: {
marginHorizontal: MARGIN.MD,
marginBottom: MARGIN.MD,
justifyContent: 'flex-end',
},
image: {
manonpalin marked this conversation as resolved.
Show resolved Hide resolved
height: 160,
resizeMode: 'contain',
marginTop: MARGIN.XL,
alignSelf: 'center',
},
icon: {
alignContent: 'center',
marginHorizontal: MARGIN.MD,
},
errorContainer: {
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
},
upperContainer: {
justifyContent: 'flex-start',
},
});

export default styles;
72 changes: 72 additions & 0 deletions src/components/AttendanceSheetSummary/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ScrollView, View, Text, BackHandler, Image } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
import { useCallback, useEffect } from 'react';
import styles from './styles';
manonpalin marked this conversation as resolved.
Show resolved Hide resolved
import NiPrimaryButton from '../form/PrimaryButton';
import FeatherButton from '../icons/FeatherButton';
import { ICON } from '../../styles/metrics';
import { GREY } from '../../styles/colors';
import NiErrorMessage from '../../components/ErrorMessage';
import MultipleCheckboxList from '../form/MultipleCheckboxList';
import { DataOptionsType } from '../../store/attendanceSheets/slice';
import Checkbox from '../form/Checkbox';
import { ErrorStateType } from '../../reducers/error';

interface AttendanceSheetSummaryProps {
goToNextScreen: () => void,
stepsName: string[],
slotsOptions: DataOptionsType[][],
signature: string,
isLoading: boolean,
setConfirmation: () => void,
confirmation: boolean,
error: ErrorStateType,
traineeName: string,
}

const AttendanceSheetSummary = ({
goToNextScreen,
stepsName,
slotsOptions,
signature,
isLoading,
setConfirmation,
confirmation,
error,
traineeName,
}: AttendanceSheetSummaryProps) => {
const navigation = useNavigation();
const checkedList = slotsOptions.flat().map(option => option.value);

const hardwareBackPress = useCallback(() => {
navigation.goBack();
return true;
}, [navigation]);

useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', hardwareBackPress);

return () => { BackHandler.removeEventListener('hardwareBackPress', hardwareBackPress); };
}, [hardwareBackPress]);

return <SafeAreaView style={styles.safeArea} edges={['top']}>
<View style={styles.header}>
<FeatherButton name='arrow-left' onPress={() => navigation.goBack()} size={ICON.MD} color={GREY[600]} />
</View>
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
<Text style={styles.title}>Emargements pour {traineeName}</Text>
<MultipleCheckboxList optionsGroups={slotsOptions} disabled groupTitles={stepsName} checkedList={checkedList} />
<Image source={{ uri: signature }} style={styles.image} />
</ScrollView>
<View style={styles.checkboxContainer}>
<Checkbox itemLabel={'Je certifie que les informations ci-dessus sont exactes'} isChecked={confirmation}
onPressCheckbox={setConfirmation} />
</View>
<View style={styles.button}>
<NiErrorMessage message={error.message} show={error.value} />
<NiPrimaryButton caption={'Suivant'} onPress={goToNextScreen} loading={isLoading} />
</View>
</SafeAreaView>;
};
export default AttendanceSheetSummary;
41 changes: 41 additions & 0 deletions src/components/AttendanceSheetSummary/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { StyleSheet } from 'react-native';
import { BUTTON_HEIGHT, MARGIN, PADDING } from '../../styles/metrics';
import { FIRA_SANS_BOLD } from '../../styles/fonts';
import { GREY } from '../../styles/colors';

const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: GREY[100],
},
header: {
paddingTop: PADDING.LG,
paddingHorizontal: PADDING.LG,
alignItems: 'center',
flexDirection: 'row',
},
container: {
flexGrow: 1,
marginHorizontal: MARGIN.LG,
},
title: {
...FIRA_SANS_BOLD.LG,
marginVertical: MARGIN.LG,
},
checkboxContainer: {
manonpalin marked this conversation as resolved.
Show resolved Hide resolved
marginHorizontal: MARGIN.MD,
backgroundColor: GREY[100],
},
button: {
marginHorizontal: MARGIN.MD,
marginBottom: MARGIN.MD,
height: BUTTON_HEIGHT + 2 * MARGIN.MD,
justifyContent: 'flex-end',
},
image: {
height: 300,
resizeMode: 'contain',
},
});

export default styles;
84 changes: 84 additions & 0 deletions src/components/AttendanceSignatureContainer/canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { IS_WEB } from '../../core/data/constants';

export const htmlContent = `
<html>
<head>
<style>
.wrapper {
position: relative;
width: 100%;
height: 100%;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.signature-pad {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: white;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/signature_pad.umd.min.js"></script>
</head>
<body>
<div class="wrapper">
<canvas id="signature-pad" class="signature-pad"></canvas>
manonpalin marked this conversation as resolved.
Show resolved Hide resolved
</div>
</body>
<script>
let canvas = document.getElementById('signature-pad');

const resizeCanvas = () => {
if ((canvas.width !== canvas.offsetWidth) || (canvas.height !== canvas.offsetHeight)) {
const ratio = 1;
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
}
};
window.onresize = resizeCanvas;
resizeCanvas();

let signaturePad = new SignaturePad(canvas, { minWidth: 2, maxWidth: 2 });

const clearSignature = () => {
signaturePad.clear();
if (${IS_WEB}) window.parent.postMessage('', "*");
else window.ReactNativeWebView.postMessage('');
};

const undoSignature = () => {
const data = signaturePad.toData();
if (data) {
data.pop();
signaturePad.fromData(data);
if (signaturePad.isEmpty()) {
signaturePad.clear();
if (${IS_WEB}) window.parent.postMessage('', "*");
else window.ReactNativeWebView.postMessage('');
} else {
const dataUrl = signaturePad.toDataURL('image/png');
if (${IS_WEB}) window.parent.postMessage(dataUrl, "*");
else window.ReactNativeWebView.postMessage(dataUrl);
}
}
};

const handleMessage = (message) => {
if (message === 'clear') clearSignature();
else if (message === 'undo') undoSignature();
};

signaturePad.addEventListener('endStroke', () => {
const dataUrl = signaturePad.toDataURL('image/png');
if (${IS_WEB}) window.parent.postMessage(dataUrl, "*");
else window.ReactNativeWebView.postMessage(dataUrl);
});
window.addEventListener('message', (event) => handleMessage(event.data));
</script>
</html>
`;
Loading
Loading