Anul 3 Semestrul 1

This commit is contained in:
2025-02-06 20:33:26 +02:00
parent 0b130ee18c
commit 184f3bd92e
313 changed files with 348499 additions and 0 deletions
@@ -0,0 +1,38 @@
import { RootState } from "@/redux/store";
import { useState, useEffect } from "react";
import { Snackbar } from "react-native-paper";
import { useDispatch, useSelector } from "react-redux";
export const ConnectionNotification = () => {
const dispatch = useDispatch();
const status = useSelector((state: RootState) => state.status);
const [visible, setVisible] = useState(false);
useEffect(() => {
if (!status.isConnected || !status.isOnline) {
setVisible(true);
}
else{
setVisible(false);
}
}, [status.isConnected, status.isOnline]);
const onDismiss = () => {
setVisible(false);
};
return (
<Snackbar
wrapperStyle={{ top: 50, left: "50%", position: 'absolute', transform: [{ translateX: "-42%" }], zIndex: 9999}}
visible={visible}
onDismiss={onDismiss}
style={{ zIndex: 9999, }}
action={{
label: 'Dismiss',
onPress: () => {
setVisible(false);
},
}}
duration={60000}
>
No internet connection
</Snackbar>
);
}
@@ -0,0 +1,17 @@
import { initDB } from "@/database/localdb";
import { useEffect } from "react";
import { View } from "react-native";
import { useDispatch } from "react-redux";
export const DataLoader = () => {
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'drug/initialize' });
dispatch({ type: 'drug/fetch' });
};
fetchData();
}, [dispatch]);
return (<View></View>);
};
@@ -0,0 +1,31 @@
import { StyleSheet } from "react-native";
import React from "react";
import { Button, Dialog, MD3Theme, Portal, useTheme, Text } from "react-native-paper";
import { useNavigation } from "@react-navigation/native";
export default function DeletePopUp(props: {drugId: number, onCancel: () => void, onDelete: () => void}) {
const theme = useTheme();
const style = styles(theme);
const navigation = useNavigation<any>(); //TODO: types...
return (
<Portal>
<Dialog
visible={true}
>
<Dialog.Title>Delete Drug</Dialog.Title>
<Dialog.Content>
<Text variant="bodyMedium">Are you sure you want to delete this drug? This action can not be reversed</Text>
</Dialog.Content>
<Dialog.Actions>
<Button contentStyle={{paddingHorizontal: 8}} mode="contained" onPress={props.onCancel}>Cancel</Button>
<Button contentStyle={{paddingHorizontal: 8}} mode="contained" onPress={props.onDelete}>Delete</Button>
</Dialog.Actions>
</Dialog>
</Portal>
);
}
const styles = (theme: MD3Theme) => StyleSheet.create({
})
@@ -0,0 +1,204 @@
import { Drug } from "@/redux/features/drug/drug";
import { View, Text, StyleSheet } from "react-native";
import { Button, Divider, MD3Theme, useTheme } from "react-native-paper";
import { useSelector } from "react-redux";
import { RootState } from "@/redux/store";
export default function DrugDisplay(props: {drug: Drug, onUpdate: (drugId: number) => void, onDelete: (drugId: number) => void}) {
const theme = useTheme();
const style = styles(theme);
const status = useSelector((state: RootState) => state.status);
return (
<View className="box" style={style.outerbox}>
<View className="box" style={style.boxBackground} >
</View>
<View className="column" style={style.boxForeground}>
<View style={style.rowHeadlineMargin}>
<View className="row" style={style.rowHeadline}>
<View className="column" style={style.column}>
<Text style={style.nameText}>{props.drug.name}</Text>
<Text style={style.manufacturerText}>{props.drug.manufacturer}</Text>
</View>
<View className="row" style={style.rowCTA}>
<Button disabled={!status.isConnected || !status.isOnline} contentStyle={{paddingHorizontal: 8}} textColor={theme.colors.primary} buttonColor={theme.colors.surface} style={style.editButton} onPress={() => props.onUpdate(props.drug.id)}>Edit</Button>
<Button contentStyle={{paddingHorizontal: 8}} textColor={theme.colors.onPrimary} buttonColor={theme.colors.primary} onPress={() => props.onDelete(props.drug.id)} >Delete</Button>
</View>
</View>
</View>
<Divider style={style.horizontalDivider} />
<View style={style.chipMargin}>
<View className="row" style={style.chip}>
<View className="box" style={style.labelChip}>
<Text style={style.label}>Category:</Text>
</View>
<View className="box" style={style.valueChip}>
<Text style={style.value}>{props.drug.category}</Text>
</View>
</View>
</View>
<View style={style.chipMargin}>
<View className="row" style={style.chip}>
<View className="box" style={style.labelChip}>
<Text style={style.label}># of Units:</Text>
</View>
<View className="box" style={style.valueChip}>
<Text style={style.value}>{props.drug.numberOfUnits.toFixed(0)}</Text>
</View>
</View>
</View>
<View style={{...style.chipMargin, ...style.lastChipMargin}}>
<View className="row" style={style.chip}>
<View className="box" style={style.labelChip}>
<Text style={style.label}>Price:</Text>
</View>
<View className="box" style={style.valueChip}>
<Text style={style.value}>${props.drug.price.toFixed(2)}</Text>
</View>
</View>
</View>
</View>
</View>
);
}
const styles = (theme: MD3Theme) => StyleSheet.create({
boxBackground: {
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor: theme.colors.surface,
borderRadius: 12,
borderColor: theme.colors.outlineVariant,
borderWidth: 1,
zIndex: 10,
},
boxForeground: {
position: "relative",
display: "flex",
flexDirection: "column",
top: 0,
left: 0,
zIndex: 20,
},
outerbox: {
position: "relative",
width: "100%",
marginBottom: 8
},
rowHeadlineMargin:{
marginTop: 16,
marginRight: 16,
marginBottom: 7,
marginLeft: 16,
},
rowHeadline: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
height: 44,
},
column:{
display: "flex",
flexDirection: "column",
},
rowCTA: {
display: "flex",
flexDirection: "row",
gap: 8,
},
nameText:{
color: theme.colors.onSurface,
fontFamily: theme.fonts.bodyLarge.fontFamily,
fontSize: theme.fonts.bodyLarge.fontSize,
lineHeight: theme.fonts.bodyLarge.lineHeight,
letterSpacing: theme.fonts.bodyLarge.letterSpacing,
fontWeight: theme.fonts.bodyLarge.fontWeight,
fontStyle: theme.fonts.bodyLarge.fontStyle
},
manufacturerText:{
color: theme.colors.onSurfaceVariant,
fontFamily: theme.fonts.bodyMedium.fontFamily,
fontSize: theme.fonts.bodyMedium.fontSize,
lineHeight: theme.fonts.bodyMedium.lineHeight,
letterSpacing: theme.fonts.bodyMedium.letterSpacing,
fontWeight: theme.fonts.bodyMedium.fontWeight,
fontStyle: theme.fonts.bodyMedium.fontStyle
},
editButton:{
borderWidth: 1,
borderColor: theme.colors.outline,
},
horizontalDivider:{
marginRight: 16,
marginBottom: 16,
marginLeft: 7,
},
chipMargin:{
marginLeft: 16,
marginRight: 16,
marginBottom: 7,
},
chip: {
display: "flex",
flexDirection: "row",
shadowRadius: 12, //TODO: shadow wack
shadowOffset: { width: 2, height: 2 },
width: "100%" ,
height: 44,
backgroundColor: theme.colors.surfaceDisabled,
borderRadius: 12
},
lastChipMargin: {
marginBottom: 40
},
labelChip: {
height: "100%",
width: "38%",
backgroundColor: theme.colors.primary,
borderTopLeftRadius: 12,
borderBottomLeftRadius: 12,
display: "flex",
alignItems: "center",
justifyContent: "center"
},
label: {
color: theme.colors.onPrimary,
textAlign: "center",
fontFamily: theme.fonts.titleSmall.fontFamily,
fontSize: theme.fonts.titleSmall.fontSize,
lineHeight: theme.fonts.titleSmall.lineHeight,
letterSpacing: theme.fonts.titleSmall.letterSpacing,
fontWeight: theme.fonts.titleSmall.fontWeight,
fontStyle: theme.fonts.titleSmall.fontStyle
},
valueChip: {
height: "100%",
width: "62%",
display: "flex",
alignItems: "center",
justifyContent: "center"
},
value: {
color: theme.colors.onSurfaceVariant,
textAlign: "center",
fontFamily: theme.fonts.bodyMedium.fontFamily,
fontSize: theme.fonts.bodyMedium.fontSize,
lineHeight: theme.fonts.bodyMedium.lineHeight,
letterSpacing: theme.fonts.bodyMedium.letterSpacing,
fontWeight: theme.fonts.bodyMedium.fontWeight,
fontStyle: theme.fonts.bodyMedium.fontStyle
},
});
@@ -0,0 +1,87 @@
import { Drug } from "@/redux/features/drug/drug";
import { View, StyleSheet, FlatList, Dimensions } from "react-native";
import { RootState, AppDispatch } from "@/redux/store";
import { useSelector, useDispatch } from "react-redux";
import React, { useState } from "react";
import DrugDisplay from "./DrugDisplay";
import { FAB, MD3Theme, useTheme } from "react-native-paper";
import { useNavigation } from "@react-navigation/native";
import DeletePopUp from "./DeletePopUp";
import { drugSlice, removeDrug } from "@/redux/features/drug/drugSlice";
import { ConnectionNotification } from "./ConnectionNotification";
import { ErrorNotification } from "./ErrorNotification";
export default function DrugList() {
const drugs: Drug[] = useSelector((state: RootState) => state.drug.drugList);
const dispatch: AppDispatch = useDispatch();
const theme = useTheme();
const style = styles(theme);
const navigation = useNavigation<any>(); //TODO: types...
const [deleteIndex, useDeleteIndex] = useState(-1)
return (
<View style={style.listMargin}>
<ConnectionNotification />
{/* <ErrorNotification /> */}
<FAB
color={theme.colors.onPrimary}
style={style.fab}
icon="pencil-outline"
mode="elevated"
onPress={() => {
navigation.navigate("add");
}}
/>
{deleteIndex != -1 ?
<DeletePopUp
drugId={deleteIndex}
onCancel = {() => {
useDeleteIndex(-1)
}}
onDelete = {() => {
dispatch({type: "drug/remove", payload: deleteIndex})
useDeleteIndex(-1)
}}
/> : null}
<FlatList style={style.list}
data={drugs}
renderItem={({item})=><DrugDisplay
key={item.id}
drug={item}
onUpdate={(drugId: number) => {
navigation.navigate("update/[id]", {id: item.id});
}}
onDelete={(drugId: number) => {
useDeleteIndex(drugId)
}} />}
keyExtractor={item => item.id.toString()}
showsHorizontalScrollIndicator={false}
/>
</View>
);
}
const styles = (theme: MD3Theme) => StyleSheet.create({
listMargin:{
paddingTop: 75.5,
paddingBottom: 23.5,
paddingLeft: 26,
paddingRight: 26,
height: Dimensions.get('window').height,
flex: 1
},
list: {
backgroundColor: theme.colors.surface,
height: "100%"
},
fab: {
position: 'absolute',
margin: 48,
right: 0,
bottom: 0,
zIndex: 30,
borderRadius: 30,
backgroundColor: theme.colors.primary
}
})
@@ -0,0 +1,39 @@
import { RootState } from "@/redux/store";
import { useState, useEffect } from "react";
import { View } from "react-native";
import { Snackbar } from "react-native-paper";
import { useDispatch, useSelector } from "react-redux";
export const ErrorNotification = () => {
const dispatch = useDispatch();
const status = useSelector((state: RootState) => state.errorStatus);
const [visible, setVisible] = useState(false);
useEffect(() => {
if (status.status) {
setVisible(true);
}
else{
setVisible(false);
}
}, [status.message]);
const onDismiss = () => {
dispatch({type: "errorStatus/setErrorStatus", payload: {status: false, message: ""}})
};
return (
<View style={{ zIndex: 9999, flex: 1, justifyContent: 'center', position: 'absolute', width: '100%', bottom: 0, left: "50%", transform: [{ translateX: "-42%" }]}}>
<Snackbar
visible={visible}
onDismiss={onDismiss}
style={{ zIndex: 9999}}
action={{
label: 'Dismiss',
onPress: () => {
setVisible(false);
},
}}
>
{status.message}
</Snackbar>
</View>
);
}
@@ -0,0 +1,202 @@
import { Drug } from "@/redux/features/drug/drug";
import { AppDispatch, RootState } from "@/redux/store";
import { useNavigation } from "@react-navigation/native";
import { useEffect, useState } from "react";
import { Dimensions, Text, View, StyleSheet } from "react-native";
import { Button, HelperText, MD3Theme, TextInput, useTheme } from "react-native-paper";
import { useDispatch, useSelector } from "react-redux";
export default function Form(props: {drugId?: number}) {
const [drug, setDrug] = useState<Drug>({
id: -1,
name: "",
manufacturer: "",
category: "",
price: 0,
numberOfUnits: 0
});
const [price , setPrice] = useState("0");
const updateDrug = useSelector((state: RootState) => state.drug.drugList.find(drug => drug.id === props.drugId));
const dispatch: AppDispatch = useDispatch();
const theme = useTheme();
const style = styles(theme);
const navigation = useNavigation<any>(); //TODO: types...
useEffect(() => {
if(props.drugId){
if(updateDrug){
setDrug(updateDrug);
setPrice(updateDrug.price.toFixed(2));
}
}
}, [updateDrug]);
const onSubmit = () => {
if(props.drugId){
dispatch({type: "drug/update", payload: drug});
}
else{
dispatch({type: "drug/add", payload: drug});
}
}
const isDisabled = () => {
return drug.name === "" || drug.manufacturer === "" || drug.category === "" || drug.price === 0 || drug.numberOfUnits === 0;
}
return (
<View style={style.mainMargin}>
<View style={style.column}>
<View style={style.chipMargin}>
<View style={style.chip}>
<Text style={style.chipLabel}>{props.drugId ? "Update Drug" : "Add Drug"}</Text>
</View>
</View>
<View style={style.fieldList}>
<View style={style.inputWrapper}>
<TextInput
label="Name"
value={drug?.name}
onChangeText={(text) => {
setDrug({...drug, name: text} as Drug)
}}
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, name: ""} as Drug)}/>}
/>
<HelperText type="error" visible={true}>*required</HelperText>
</View>
<View style={style.inputWrapper}>
<TextInput
label="Manufacturer"
value={drug?.manufacturer}
onChangeText={(text) => {
setDrug({...drug, manufacturer: text} as Drug)
}}
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, manufacturer: ""} as Drug)}/>}
/>
<HelperText type="error" visible={true}>*required</HelperText>
</View>
<View style={style.inputWrapper}>
<TextInput
label="Category"
value={drug?.category}
onChangeText={(text) => {
setDrug({...drug, category: text} as Drug)
}}
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, category: ""} as Drug)}/>}
/>
<HelperText type="error" visible={true}>*required</HelperText>
</View>
<View style={style.inputWrapper}>
<TextInput
label="Number of Units"
value={drug?.numberOfUnits.toString()}
keyboardType="numeric"
onChangeText={(text) => {
if(text === ""){
setDrug({...drug, numberOfUnits: 0} as Drug)
}
else{
setDrug({...drug, numberOfUnits: parseInt(text)} as Drug)
}
}}
right={<TextInput.Icon icon="close" onPress={() => setDrug({...drug, numberOfUnits: 0} as Drug)}/>}
/>
<HelperText type="error" visible={true}>*required</HelperText>
</View>
<View style={style.inputWrapper}>
<TextInput
label="Price"
value={price}
keyboardType="numeric"
onChangeText={(text) => {
setPrice(text);
if(text === "" || isNaN(Number(text)) || text[text.length - 1] === '.'){
setDrug({...drug, price: 0} as Drug)
}
else{
setPrice(parseFloat(text).toString());
setDrug({...drug, price: parseFloat(text)} as Drug)
}
}}
right={<TextInput.Icon icon="close" onPress={() => {setDrug({...drug, price: 0} as Drug); setPrice("")}}/>}
left={<TextInput.Affix text="$ " />}
/>
<HelperText type="error" visible={true}>*required</HelperText>
</View>
</View>
<View style={style.buttonRow}>
<Button mode="outlined" onPress={() => navigation.navigate("index")}>Cancel</Button>
<Button mode="contained"
textColor={theme.colors.onPrimary}
buttonColor={theme.colors.primary}
onPress={() => {
if(!isDisabled())
{
onSubmit();
navigation.navigate("index")
}
}}>{props.drugId ? "Update" : "Add"}</Button>
</View>
</View>
</View>
);
}
const styles = (theme: MD3Theme) => StyleSheet.create({
mainMargin:{
paddingTop: 63,
paddingBottom: 62,
paddingLeft: 62,
paddingRight: 73,
height: Dimensions.get('window').height,
flex: 1
},
column: {
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.colors.surface,
},
chipMargin: {
paddingBottom: 25,
paddingLeft: 61,
paddingRight: 61,
},
chip: {
height: 49,
width: "100%",
borderRadius: 8,
borderWidth: 1,
borderColor: theme.colors.outlineVariant,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
},
chipLabel: {
color: theme.colors.onSurface,
textAlign: "center",
fontFamily: theme.fonts.labelLarge.fontFamily,
fontSize: theme.fonts.labelLarge.fontSize,
lineHeight: theme.fonts.labelLarge.lineHeight,
letterSpacing: theme.fonts.labelLarge.letterSpacing,
fontWeight: theme.fonts.labelLarge.fontWeight,
fontStyle: theme.fonts.labelLarge.fontStyle
},
fieldList: {
display: 'flex',
flexDirection: 'column',
marginBottom: 30,
width: "100%"
},
buttonRow: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
gap: 8
},
inputWrapper: {
marginBottom: 16,
width: "100%"
}
})