DONE: first init

This commit is contained in:
ISMAIL MASSERAN
2026-07-20 16:10:22 +08:00
commit 3189e3a1e3
272 changed files with 48853 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
import React, { useState } from "react";
import {
View,
Text,
FlatList,
TouchableOpacity,
StyleSheet,
Dimensions,
} from "react-native";
import { Screens } from "../constants/screens";
import { getBranchServices } from "../services/fetchData";
import { Colors } from "../constants/colors";
import { Fonts } from "../constants/fonts";
import { Ionicons } from "@expo/vector-icons";
import { useFonts } from "expo-font";
import { Assets } from "../constants/assets";
const windowWidth = Dimensions.get("window").width;
const windowHeight = Dimensions.get("window").height;
export default function BranchPickScreen({
route,
navigation,
}: {
route: any;
navigation: any;
}) {
const { branches } = route.params;
let [fontsLoaded] = useFonts({
[Fonts.ARIAL]: Assets.ARIAL,
[Fonts.TIMES_NEW_ROMAN]: Assets.TIMES_NEW_ROMAN,
[Fonts.VERDANA]: Assets.VERDANA,
[Fonts.HELVETICA]: Assets.HELVETICA,
[Fonts.MONTSERRAT]: Assets.MONTSERRAT,
[Fonts.CALIBRI]: Assets.CALIBRI,
[Fonts.FUTURA]: Assets.FUTURA,
[Fonts.BODONI]: Assets.BODONI,
[Fonts.ROCKWELL]: Assets.ROCKWELL,
[Fonts.COMIC_SANS_MS]: Assets.COMIC_SANS_MS,
});
const renderItem = ({ item }: { item: any }) => (
<TouchableOpacity
style={styles.branchItem}
onPress={() => handlePress(item.id)}
>
<Text style={styles.branchName}>{item.name}</Text>
<View style={styles.iconContainer}>
<Ionicons
name="chevron-forward-circle-outline"
size={windowWidth * 0.1}
color={Colors.ACCENT}
/>
</View>
</TouchableOpacity>
);
async function handlePress(id: any) {
getBranchServices(route.params.code, id)
.then(function (services) {
navigation.navigate(Screens.WELCOME, {
details: route.params.details,
services: services,
branchID: id,
});
})
.catch((error) => {
console.error("Error:", error);
});
}
if (!fontsLoaded) return <Text>Loading...</Text>;
return (
<View style={styles.container}>
<Text style={[styles.title]}>Poslovnice</Text>
<FlatList
data={branches}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={styles.flatListContainer}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.BACKGROUND,
justifyContent: "center",
paddingTop: windowHeight * 0.1,
},
title: {
fontSize: windowWidth * 0.1,
fontWeight: "bold",
marginBottom: windowHeight * 0.03,
marginLeft: windowWidth * 0.05,
fontFamily: Fonts.ARIAL,
},
flatListContainer: {
flexGrow: 1,
paddingHorizontal: windowWidth * 0.05,
paddingBottom: windowHeight * 0.05,
},
branchItem: {
paddingHorizontal: windowWidth * 0.05,
paddingVertical: windowHeight * 0.06, // Adjust the padding to make the items taller
marginBottom: windowHeight * 0.02,
borderRadius: windowWidth * 0.05,
backgroundColor: "#fff",
shadowColor: Colors.ACCENT,
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
branchContent: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
},
branchName: {
flex: 1,
fontSize: windowWidth * 0.08,
fontFamily: Fonts.ARIAL,
textAlign: "center",
},
icon: {
marginLeft: windowWidth * 0.02,
},
iconContainer: {
alignItems: "center",
marginTop: windowHeight * 0.02, // Adjust the margin to position the icon below the branch name
},
});
+127
View File
@@ -0,0 +1,127 @@
import React, { useState } from "react";
import {
SafeAreaView,
StyleSheet,
TextInput,
Pressable,
Text,
Alert,
ActivityIndicator,
View,
} from "react-native";
import { registerRootComponent } from "expo";
import { getCompanyBranches, getCompanyDetails } from "../services/fetchData";
import { Dialogs } from "../constants/dialogs";
import { Colors } from "../constants/colors";
import { Screens } from "../constants/screens";
import { Dimensions } from 'react-native';
const screenWidth = Dimensions.get('window').width;
const buttonWidth = screenWidth * 0.5;
export default function CodeEnterScreen({ navigation }: { navigation: any }) {
const [text, onChangeText] = React.useState("");
const [isLoading, setIsLoading] = useState(false);
function displayInvalidCodeAlert() {
Alert.alert(
Dialogs.ERROR.INVALID_CODE,
Dialogs.ERROR.INVALID_CODE_DESC,
[{ text: Dialogs.BUTTON.OK }],
{ cancelable: false }
);
}
async function handlePress() {
setIsLoading(true);
// await new Promise((resolve) => setTimeout(resolve, 2000));
const code = text;
getCompanyDetails(code)
.then(function (details) {
setIsLoading(false);
getCompanyBranches(code)
.then(function (branches){
navigation.navigate(Screens.BRANCH_PICK, {
details: details, branches: branches, code
});})
})
.catch((error) => {
console.error("Error:", error);
setIsLoading(false);
displayInvalidCodeAlert();
});
}
return (
<SafeAreaView style={styles.area}>
{isLoading && <ActivityIndicator style={styles.loader} size={75} />}
{isLoading && <View style={styles.loadingArea}></View>}
<TextInput
style={styles.input}
onChangeText={(text) => onChangeText(text)}
value={text}
placeholder={Dialogs.PROMPT.ENTER_CODE}
/>
<Pressable
style={({ pressed }) => [
styles.button,
pressed ? styles.buttonPressed : null,
]}
onPress={handlePress}
>
<Text style={styles.text}>{Dialogs.BUTTON.SUBMIT}</Text>
</Pressable>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
loadingArea: {
position: "absolute",
zIndex: 2,
height: "100%",
width: "100%",
backgroundColor: "rgba(255, 255, 255, 0.75)",
},
loader: {
position: "absolute",
zIndex: 100,
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
textAlign: "center",
width: 220,
},
area: {
justifyContent: "center",
alignItems: "center",
flex: 1,
},
button: {
alignItems: "center",
justifyContent: "center",
paddingVertical: 12,
paddingHorizontal: 32,
borderRadius: 4,
elevation: 3,
width: buttonWidth, // Adjusted dynamically based on screen width
backgroundColor: Colors.ACCENT,
},
buttonPressed: {
backgroundColor: Colors.PRIMARY,
},
text: {
fontSize: 16,
lineHeight: 21,
fontWeight: "bold",
letterSpacing: 0.25,
color: "white",
},
});
registerRootComponent(({ navigation }) => <CodeEnterScreen navigation={navigation} />);
@@ -0,0 +1,64 @@
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, Dimensions } from 'react-native';
import { Fonts } from '../constants/fonts';
import { Colors } from '../constants/colors';
const windowWidth = Dimensions.get('window').width;
const windowHeight = Dimensions.get('window').height;
export default function TicketInfoScreen({ route }: {route: any}) {
const ticket = route.params.ticket
console.log(ticket)
useEffect(() => {
}, []);
return (
<View style={styles.container}>
<Text style={styles.title}>Ticket Details</Text>
{ticket ? (
<View style={styles.detailsContainer}>
<Text style={styles.detailText}>Naziv: {ticket.name}</Text>
<Text style={styles.detailText}>Generisano: {new Date(ticket.date).toLocaleString()}</Text>
<Text style={styles.detailText}>Podići na šalterima:</Text>
{ticket.stations.map((station: string, index: number) => (
<Text key={index} style={styles.stationText}>{"- "+ station}</Text>
))}
</View>
) : (
<Text>Loading...</Text>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.BACKGROUND,
justifyContent: 'flex-start',
paddingHorizontal: windowWidth * 0.05,
paddingTop: windowHeight * 0.2,
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: windowHeight * 0.03,
fontFamily: Fonts.ARIAL,
alignSelf: 'center',
},
detailsContainer: {
marginTop: windowHeight * 0.03,
},
detailText: {
fontSize: 20,
marginBottom: 10,
fontFamily: Fonts.ARIAL,
},
stationText: {
fontSize: 20,
marginBottom: 5,
fontFamily: Fonts.ARIAL,
marginLeft: 10,
}
});
+154
View File
@@ -0,0 +1,154 @@
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet, Dimensions, ActivityIndicator } from 'react-native';
import { Screens } from '../constants/screens';
import { getTickets } from '../services/fetchData';
import { Colors } from '../constants/colors';
import { Fonts } from '../constants/fonts';
import { Ionicons } from '@expo/vector-icons';
import { getExpoToken } from '../utils/tokenUtils';
const windowWidth = Dimensions.get('window').width;
const windowHeight = Dimensions.get('window').height;
export default function TicketListScreen({ navigation }: { navigation: any }){
const [tickets, setTickets] = useState<any[]>([]);
useEffect(() => {
const fetchTickets = async () => {
try {
const fetchedTickets = await getTickets(getExpoToken());
setTickets(fetchedTickets);
} catch (error) {
console.error('Error fetching tickets:', error);
}
};
fetchTickets();
const intervalId = setInterval(fetchTickets, 10000);
return () => clearInterval(intervalId);
}, []);
const renderItem = ({ item }: { item: any }) => {
return (
<TouchableOpacity
style={styles.ticketItem}
onPress={() => handlePress(item.id)}
>
<Text style={styles.ticketName}>{item.name}</Text>
<View style={styles.stationContainer}>
<Text style={styles.stationTitle}>Šalteri</Text>
{item.stations.map((station: string, index: number) => (
<Text key={index} style={styles.stationList}>{station}</Text>
))}
</View>
<View style={styles.iconContainer}>
<Ionicons name="information-circle-outline" size={windowWidth * 0.1} color={Colors.ACCENT} />
</View>
</TouchableOpacity>
);
};
async function handlePress(id: any) {
let ticket = tickets.find(item => item.id == id)
navigation.navigate(Screens.TICKET_INFO, {
ticket:ticket
})
}
if (tickets.length === 0) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={Colors.PRIMARY} />
</View>
);
} else {
return (
<View style={styles.container}>
<Text style={styles.title}>Tiketi</Text>
<FlatList
data={tickets}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={styles.flatListContainer}
/>
</View>
);
}
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.BACKGROUND,
justifyContent: 'center',
paddingTop: windowHeight * 0.1,
},
title: {
fontSize: windowWidth * 0.1,
fontWeight: 'bold',
marginBottom: windowHeight * 0.03,
marginLeft: windowWidth * 0.05,
fontFamily: Fonts.ARIAL,
},
flatListContainer: {
flexGrow: 1,
paddingHorizontal: windowWidth * 0.05,
paddingBottom: windowHeight * 0.05,
},
ticketItem: {
paddingHorizontal: windowWidth * 0.05,
paddingVertical: windowHeight * 0.06,
marginBottom: windowHeight * 0.02,
borderRadius: windowWidth * 0.05,
backgroundColor: '#fff',
shadowColor: Colors.ACCENT,
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
ticketContent: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
ticketName: {
flex: 1,
fontSize: windowWidth * 0.08,
fontFamily: Fonts.ARIAL,
textAlign: 'center',
},
icon: {
marginLeft: windowWidth * 0.02,
},
iconContainer: {
alignItems: 'center',
marginTop: windowHeight * 0.02,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
stationList: {
fontSize: windowWidth * 0.05,
color: 'gray',
marginTop: 5,
},
stationTitle: {
fontSize: windowWidth * 0.06,
fontWeight: 'bold',
marginBottom: 5,
},
stationContainer: {
marginTop: 30,
},
});
+256
View File
@@ -0,0 +1,256 @@
import React, { useEffect, useRef, useState } from "react";
import {
View,
StyleSheet,
Animated,
Text,
FlatList,
TouchableOpacity,
Platform,
Alert,
} from "react-native";
import WelcomeMessage from "../components/WelcomeMessage";
import { useFonts } from "expo-font";
const { Fonts } = require("../constants/fonts");
const { Assets } = require("../constants/assets");
const { Colors } = require("../constants/colors");
import { Ionicons } from "@expo/vector-icons";
import AssignedNumberAlert from "../components/AssignedNumberAlert";
//import * as Notifications from "expo-notifications";
//import * as Device from "expo-device";
import { getExpoToken, setExpoToken } from "../utils/tokenUtils";
import { generateTicket } from "../services/fetchData";
export default function WelcomeScreen({ route }: { route: any }) {
const { details } = route.params;
let { services } = route.params;
let { branchID } = route.params;
let { name, welcomeMessage, font, logoUrl } = details;
/*
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
*/
let [fontsLoaded] = useFonts({
[Fonts.ARIAL]: Assets.ARIAL,
[Fonts.TIMES_NEW_ROMAN]: Assets.TIMES_NEW_ROMAN,
[Fonts.VERDANA]: Assets.VERDANA,
[Fonts.HELVETICA]: Assets.HELVETICA,
[Fonts.MONTSERRAT]: Assets.MONTSERRAT,
[Fonts.CALIBRI]: Assets.CALIBRI,
[Fonts.FUTURA]: Assets.FUTURA,
[Fonts.BODONI]: Assets.BODONI,
[Fonts.ROCKWELL]: Assets.ROCKWELL,
[Fonts.COMIC_SANS_MS]: Assets.COMIC_SANS_MS,
});
const fadeAnim = React.useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 2500,
useNativeDriver: true,
}).start();
}, [fadeAnim]);
/*
useEffect(() => {
getToken();
}, []);
async function getToken() {
try {
await registerForPushNotificationsAsync().then((token) => {
if (token != undefined) setExpoToken(token);
});
} catch (error) {
console.error("Failed to fetch data in TasksScreen:", error);
}
}
*/
const [modalVisible, setModalVisible] = useState(false);
const [ticket, setTicket] = useState(0);
const openModal = () => {
setModalVisible(true);
};
const closeModal = () => {
setModalVisible(false);
};
const renderItem = ({ item }: { item: any }) => (
<TouchableOpacity
style={styles.branchItem}
onPress={() => handlePress(item.id)}
>
<View style={styles.branchInfoContainer}>
<Ionicons
name="information-circle"
size={24}
color={Colors.ACCENT}
style={styles.icon}
/>
<Text style={styles.branchName}>{item.name}</Text>
<Ionicons
name="chevron-forward"
size={24}
color={Colors.ACCENT}
style={styles.icon}
/>
</View>
</TouchableOpacity>
);
async function handlePress(serviceID: any) {
const assignTicket = async () => {
try {
const fetchedTicket = await generateTicket(
getExpoToken(),
branchID,
serviceID
);
setTicket(fetchedTicket.number);
} catch (error) {
console.error("Error fetching tickets:", error);
}
};
assignTicket();
openModal();
}
if (!fontsLoaded) return <Text>Loading...</Text>;
return (
<View style={styles.container}>
<View style={styles.logoContainer}>
<Animated.Image
style={[styles.logo, { opacity: fadeAnim }]}
source={{ uri: logoUrl }}
/>
</View>
<WelcomeMessage name={name} font={font} welcome={welcomeMessage} />
<FlatList
data={services}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={styles.flatListContainer}
/>
<AssignedNumberAlert
visible={modalVisible}
number={ticket}
onClose={closeModal}
/>
</View>
);
}
/*
async function registerForPushNotificationsAsync() {
let token;
if (Platform.OS === "android") {
await Notifications.setNotificationChannelAsync("default", {
name: "default",
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: "#FF231F7C",
});
}
if (Device.isDevice) {
const { status: existingStatus } =
await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== "granted") {
alert("Failed to get push token for push notification!");
return;
}
token = (await Notifications.getExpoPushTokenAsync()).data;
} else {
alert("Must use physical device for Push Notifications");
}
return token;
}
*/
export const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.BACKGROUND,
alignItems: "center",
justifyContent: "center",
},
titleContainer: {
alignSelf: "flex-start",
width: "100%",
},
logoContainer: {
marginBottom: 20, // Add margin to separate logo from title
fontFamily: Fonts.ARIAL,
color: Colors.TEXT_PRIMARY,
},
logo: {
marginTop: 80, // Add margin to separate logo from title
width: "40%", // Adjust the logo size to fit better on all screens
height: undefined, // Allow height to adjust automatically based on width
aspectRatio: 1, // Maintain aspect ratio
resizeMode: "contain",
},
title: {
fontSize: 42,
textAlign: "center",
marginTop: 20,
fontFamily: Fonts.ARIAL,
color: Colors.TEXT_PRIMARY,
},
flatListContainer: {
flexGrow: 1,
paddingHorizontal: 20,
paddingTop: 10, // Add padding to the top of the list to separate from title
},
branchItem: {
marginTop: 10, // Add margin between items
paddingVertical: 15,
paddingHorizontal: 20,
borderRadius: 10,
borderColor: Colors.ACCENT,
borderWidth: 1,
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
width: "100%", // Make items take full width of the screen
},
branchInfoContainer: {
flexDirection: "row", // Arrange items horizontally
alignItems: "center", // Center items vertically
},
branchName: {
fontSize: 30,
fontFamily: Fonts.ARIAL,
color: Colors.TEXT_PRIMARY,
marginLeft: 10, // Add some space between text and icon
},
icon: {
marginRight: 10, // Add some space between icon and text
},
});