Initial application

master
Ernest Litvinenko 2023-12-13 12:20:39 +03:00
parent d8f3c5deb2
commit 85d45e009b
23 changed files with 9629 additions and 16 deletions

2
.gitignore vendored
View File

@ -33,3 +33,5 @@ yarn-error.*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
.idea/

92
App.js
View File

@ -1,20 +1,84 @@
import { StatusBar } from 'expo-status-bar'; import {StyleSheet, View} from 'react-native';
import { StyleSheet, Text, View } from 'react-native'; import {NavigationContainer} from "@react-navigation/native";
import 'react-native-gesture-handler';
import {createStackNavigator} from "@react-navigation/stack";
import Home from "./screens/home";
import Login from "./screens/login";
import Event from "./screens/event";
import {createBottomTabNavigator} from "@react-navigation/bottom-tabs";
import TaskIconActive from "./assets/taskIconActive.svg";
import TaskIcon from "./assets/taskIconInactive.svg"
import NotificationIconActive from "./assets/notificationActive.svg";
import NotificationIconInactive from './assets/notificationInactive.svg'
const generateTabBarIcon = ({focused, style}) => {
const styles = {
taskIcon: {
focused: <TaskIconActive width={25} height={25}/>,
default: <TaskIcon width={25} height={25}/>
},
notificationIcon: {
focused: <NotificationIconActive width={25} height={25}/>,
default: <NotificationIconInactive width={25} height={25}/>
}
}
return !!focused ? styles[style].focused : styles[style].default
}
const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
const HomeStack = () => (
<Stack.Navigator initialRouteName={"Home"} screenOptions={{headerShown: false}}>
<Stack.Screen name="Login" component={Login}/>
<Stack.Screen name="Home" component={Home}/>
<Stack.Screen name="Event" component={Event}/>
</Stack.Navigator>
)
export default function App() { export default function App() {
return (
<View style={styles.container}> return (
<Text>Open up App.js to start working on your app!</Text> <View style={styles.container}>
<StatusBar style="auto" /> <NavigationContainer>
</View> <Tab.Navigator initialRouteName={"HomeStack"} screenOptions={{
); headerShown: false,
tabBarActiveTintColor: '#E5352D',
tabBarInactiveTintColor: "rgba(100, 101, 103, 0.5)",
tabBarLabelStyle: {fontSize: 10, fontWeight: "bold"}
}}>
<Tab.Screen name="HomeStack" component={HomeStack} options={{
tabBarLabel: "Задания",
tabBarIcon: ({focused}) => generateTabBarIcon({focused, style: "taskIcon"})
}}/>
<Tab.Screen name="Notification"
component={() => <View></View>}
options={
{
tabBarLabel: "Уведомления",
tabBarIcon: ({focused}) => generateTabBarIcon({
focused,
style: "notificationIcon"
})
}
}
/>
</Tab.Navigator>
</NavigationContainer>
</View>
);
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
backgroundColor: '#fff', backgroundColor: '#fff',
alignItems: 'center', },
justifyContent: 'center',
},
}); });

3
assets/bi_chevron.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4424 14.1925C10.3844 14.2507 10.3154 14.2969 10.2395 14.3284C10.1636 14.3599 10.0822 14.3761 9.99994 14.3761C9.91773 14.3761 9.83633 14.3599 9.7604 14.3284C9.68447 14.2969 9.6155 14.2507 9.55744 14.1925L2.05744 6.6925C1.94008 6.57514 1.87415 6.41597 1.87415 6.25C1.87415 6.08403 1.94008 5.92485 2.05744 5.8075C2.1748 5.69014 2.33397 5.62421 2.49994 5.62421C2.66591 5.62421 2.82508 5.69014 2.94244 5.8075L9.99994 12.8662L17.0574 5.8075C17.1748 5.69014 17.334 5.62421 17.4999 5.62421C17.6659 5.62421 17.8251 5.69014 17.9424 5.8075C18.0598 5.92486 18.1257 6.08403 18.1257 6.25C18.1257 6.41597 18.0598 6.57514 17.9424 6.6925L10.4424 14.1925Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 810 B

View File

@ -0,0 +1,3 @@
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 19V17H6.5V10C6.5 8.61667 6.91667 7.38734 7.75 6.312C8.58333 5.23667 9.66667 4.53267 11 4.2V3.5C11 3.08334 11.146 2.729 11.438 2.437C11.73 2.145 12.084 1.99934 12.5 2C12.9167 2 13.271 2.146 13.563 2.438C13.855 2.73 14.0007 3.084 14 3.5V4.2C15.3333 4.53334 16.4167 5.23767 17.25 6.313C18.0833 7.38834 18.5 8.61734 18.5 10V17H20.5V19H4.5ZM12.5 22C11.95 22 11.479 21.804 11.087 21.412C10.695 21.02 10.4993 20.5493 10.5 20H14.5C14.5 20.55 14.304 21.021 13.912 21.413C13.52 21.805 13.0493 22.0007 12.5 22ZM8.5 17H16.5V10C16.5 8.9 16.1083 7.95834 15.325 7.175C14.5417 6.39167 13.6 6 12.5 6C11.4 6 10.4583 6.39167 9.675 7.175C8.89167 7.95834 8.5 8.9 8.5 10V17Z" fill="#E5352D"/>
</svg>

After

Width:  |  Height:  |  Size: 788 B

View File

@ -0,0 +1,3 @@
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 19V17H6.5V10C6.5 8.61667 6.91667 7.38734 7.75 6.312C8.58333 5.23667 9.66667 4.53267 11 4.2V3.5C11 3.08334 11.146 2.729 11.438 2.437C11.73 2.145 12.084 1.99934 12.5 2C12.9167 2 13.271 2.146 13.563 2.438C13.855 2.73 14.0007 3.084 14 3.5V4.2C15.3333 4.53334 16.4167 5.23767 17.25 6.313C18.0833 7.38834 18.5 8.61734 18.5 10V17H20.5V19H4.5ZM12.5 22C11.95 22 11.479 21.804 11.087 21.412C10.695 21.02 10.4993 20.5493 10.5 20H14.5C14.5 20.55 14.304 21.021 13.912 21.413C13.52 21.805 13.0493 22.0007 12.5 22ZM8.5 17H16.5V10C16.5 8.9 16.1083 7.95834 15.325 7.175C14.5417 6.39167 13.6 6 12.5 6C11.4 6 10.4583 6.39167 9.675 7.175C8.89167 7.95834 8.5 8.9 8.5 10V17Z" fill="#646567" fill-opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 807 B

10
assets/ph_check-fill.svg Normal file
View File

@ -0,0 +1,10 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_41_1086)">
<path d="M36.9231 0H3.07692C2.26087 0 1.47824 0.383116 0.90121 1.06507C0.324175 1.74702 0 2.67194 0 3.63636V36.3636C0 37.3281 0.324175 38.253 0.90121 38.9349C1.47824 39.6169 2.26087 40 3.07692 40H36.9231C37.7391 40 38.5218 39.6169 39.0988 38.9349C39.6758 38.253 40 37.3281 40 36.3636V3.63636C40 2.67194 39.6758 1.74702 39.0988 1.06507C38.5218 0.383116 37.7391 0 36.9231 0ZM34.9346 10.3773L16.4731 32.1955C16.3302 32.3645 16.1605 32.4986 15.9738 32.5901C15.787 32.6816 15.5868 32.7287 15.3846 32.7287C15.1824 32.7287 14.9822 32.6816 14.7955 32.5901C14.6087 32.4986 14.439 32.3645 14.2962 32.1955L6.60385 23.1045C6.31517 22.7634 6.15299 22.3007 6.15299 21.8182C6.15299 21.3357 6.31517 20.873 6.60385 20.5318C6.89252 20.1907 7.28406 19.999 7.69231 19.999C8.10056 19.999 8.49209 20.1907 8.78077 20.5318L15.3846 28.3386L32.7577 7.80454C33.0464 7.46338 33.4379 7.27172 33.8462 7.27172C34.2544 7.27172 34.6459 7.46338 34.9346 7.80454C35.2233 8.14571 35.3855 8.60843 35.3855 9.09091C35.3855 9.57339 35.2233 10.0361 34.9346 10.3773Z" fill="#C0C0C0"/>
</g>
<defs>
<clipPath id="clip0_41_1086">
<rect width="40" height="40" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

11
assets/taskIconActive.svg Normal file
View File

@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_64_17)">
<path d="M10.5 15.135L7.8075 12.4425L6.75 13.5L10.5 17.25L17.25 10.5L16.1925 9.435L10.5 15.135Z" fill="#E5352D"/>
<path d="M18.75 3.75H16.5V3C16.5 2.60218 16.342 2.22064 16.0607 1.93934C15.7794 1.65804 15.3978 1.5 15 1.5H9C8.60218 1.5 8.22064 1.65804 7.93934 1.93934C7.65804 2.22064 7.5 2.60218 7.5 3V3.75H5.25C4.85218 3.75 4.47064 3.90804 4.18934 4.18934C3.90804 4.47064 3.75 4.85218 3.75 5.25V21C3.75 21.3978 3.90804 21.7794 4.18934 22.0607C4.47064 22.342 4.85218 22.5 5.25 22.5H18.75C19.1478 22.5 19.5294 22.342 19.8107 22.0607C20.092 21.7794 20.25 21.3978 20.25 21V5.25C20.25 4.85218 20.092 4.47064 19.8107 4.18934C19.5294 3.90804 19.1478 3.75 18.75 3.75ZM9 3H15V6H9V3ZM18.75 21H5.25V5.25H7.5V7.5H16.5V5.25H18.75V21Z" fill="#E5352D"/>
</g>
<defs>
<clipPath id="clip0_64_17">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 980 B

View File

@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3_163)">
<path d="M10.5 15.1351L7.8075 12.4426L6.75 13.5001L10.5 17.2501L17.25 10.5001L16.1925 9.43506L10.5 15.1351Z" fill="#646567"/>
<path d="M18.75 3.75H16.5V3C16.5 2.60218 16.342 2.22064 16.0607 1.93934C15.7794 1.65804 15.3978 1.5 15 1.5H9C8.60218 1.5 8.22064 1.65804 7.93934 1.93934C7.65804 2.22064 7.5 2.60218 7.5 3V3.75H5.25C4.85218 3.75 4.47064 3.90804 4.18934 4.18934C3.90804 4.47064 3.75 4.85218 3.75 5.25V21C3.75 21.3978 3.90804 21.7794 4.18934 22.0607C4.47064 22.342 4.85218 22.5 5.25 22.5H18.75C19.1478 22.5 19.5294 22.342 19.8107 22.0607C20.092 21.7794 20.25 21.3978 20.25 21V5.25C20.25 4.85218 20.092 4.47064 19.8107 4.18934C19.5294 3.90804 19.1478 3.75 18.75 3.75ZM9 3H15V6H9V3ZM18.75 21H5.25V5.25H7.5V7.5H16.5V5.25H18.75V21Z" fill="#646567"/>
</g>
<defs>
<clipPath id="clip0_3_163">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 992 B

10
assets/tick_danger.svg Normal file
View File

@ -0,0 +1,10 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_41_1077)">
<path d="M36.9231 0H3.07692C2.26087 0 1.47824 0.383116 0.90121 1.06507C0.324175 1.74702 0 2.67194 0 3.63636V36.3636C0 37.3281 0.324175 38.253 0.90121 38.9349C1.47824 39.6169 2.26087 40 3.07692 40H36.9231C37.7391 40 38.5218 39.6169 39.0988 38.9349C39.6758 38.253 40 37.3281 40 36.3636V3.63636C40 2.67194 39.6758 1.74702 39.0988 1.06507C38.5218 0.383116 37.7391 0 36.9231 0ZM34.9346 10.3773L16.4731 32.1955C16.3302 32.3645 16.1605 32.4986 15.9738 32.5901C15.787 32.6816 15.5868 32.7287 15.3846 32.7287C15.1824 32.7287 14.9822 32.6816 14.7955 32.5901C14.6087 32.4986 14.439 32.3645 14.2962 32.1955L6.60385 23.1045C6.31517 22.7634 6.15299 22.3007 6.15299 21.8182C6.15299 21.3357 6.31517 20.873 6.60385 20.5318C6.89252 20.1907 7.28406 19.999 7.69231 19.999C8.10056 19.999 8.49209 20.1907 8.78077 20.5318L15.3846 28.3386L32.7577 7.80454C33.0464 7.46338 33.4379 7.27172 33.8462 7.27172C34.2544 7.27172 34.6459 7.46338 34.9346 7.80454C35.2233 8.14571 35.3855 8.60843 35.3855 9.09091C35.3855 9.57339 35.2233 10.0361 34.9346 10.3773Z" fill="#C5A145"/>
</g>
<defs>
<clipPath id="clip0_41_1077">
<rect width="40" height="40" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

10
assets/tick_success.svg Normal file
View File

@ -0,0 +1,10 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_41_1068)">
<path d="M36.9231 0H3.07692C2.26087 0 1.47824 0.383116 0.90121 1.06507C0.324175 1.74702 0 2.67194 0 3.63636V36.3636C0 37.3281 0.324175 38.253 0.90121 38.9349C1.47824 39.6169 2.26087 40 3.07692 40H36.9231C37.7391 40 38.5218 39.6169 39.0988 38.9349C39.6758 38.253 40 37.3281 40 36.3636V3.63636C40 2.67194 39.6758 1.74702 39.0988 1.06507C38.5218 0.383116 37.7391 0 36.9231 0ZM34.9346 10.3773L16.4731 32.1955C16.3302 32.3645 16.1605 32.4986 15.9738 32.5901C15.787 32.6816 15.5868 32.7287 15.3846 32.7287C15.1824 32.7287 14.9822 32.6816 14.7955 32.5901C14.6087 32.4986 14.439 32.3645 14.2962 32.1955L6.60385 23.1045C6.31517 22.7634 6.15299 22.3007 6.15299 21.8182C6.15299 21.3357 6.31517 20.873 6.60385 20.5318C6.89252 20.1907 7.28406 19.999 7.69231 19.999C8.10056 19.999 8.49209 20.1907 8.78077 20.5318L15.3846 28.3386L32.7577 7.80454C33.0464 7.46338 33.4379 7.27172 33.8462 7.27172C34.2544 7.27172 34.6459 7.46338 34.9346 7.80454C35.2233 8.14571 35.3855 8.60843 35.3855 9.09091C35.3855 9.57339 35.2233 10.0361 34.9346 10.3773Z" fill="#408A3B"/>
</g>
<defs>
<clipPath id="clip0_41_1068">
<rect width="40" height="40" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/yandexNavi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 B

27
components/acordion.js Normal file
View File

@ -0,0 +1,27 @@
import {Pressable, Text, View, Image} from "react-native";
import Chevron from '../assets/bi_chevron.svg'
import {useState} from "react";
const Accordion = ({title, children, ...props}) => {
const [active, setActive] = useState(true)
return (
<View {...props}>
<Pressable onPress={() => setActive(!active)}>
<View style={{display: "flex", flexDirection: "row", alignItems: "center", marginBottom: 16 }}>
<Text style={{fontSize: 16, fontWeight: "bold", marginRight: 16, paddingTop: 16, paddingBottom: 16}}>{title}</Text>
<Chevron width={20} height={20} style={{transform: [{rotate: active ? '180deg' : '0deg'}]}}/>
{/*<Image source={require('../assets/bi_chevron.svg')} style={{width: 20, height: 20, transform: [{rotate: active ? '180deg' : '0deg'}]}} />*/}
</View>
</Pressable>
<View style={
{display: active ? 'flex' : 'none', flexDirection: 'column'}
}>
{children}
</View>
</View>
)
}
export default Accordion;

99
components/card.js Normal file
View File

@ -0,0 +1,99 @@
import {View, Text} from "react-native";
import {LinearGradient} from "expo-linear-gradient";
const Card = {
Block: ({variant, image, children}) => {
const variantSelection = {
default: ["rgba(100, 101, 103, 0.20)", "rgba(100, 101, 103, 0.0)"],
success: ["rgba(26, 117, 18, 0.20)", "rgba(26, 117, 18, 0.0)"],
danger: ['rgba(210, 145, 21, 0.20)', 'rgba(210, 145, 21, 0.0)'],
error: ["rgba(229,53,45, 0.20)", "rgba(229,53,45, 0.0)"]
}
return (
<View style={{marginBottom: 16}}>
<LinearGradient start={{x: 0, y: 0}} end={{x: 1, y: 0}}
colors={variant ? variantSelection[variant] : variantSelection.default}
style={{
padding: 16,
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between"
}}
>
<View style={{width: !!image ? "80%" : "100%"}}>
{children}
</View>
{!!image &&
<View style={{width: "10%"}}>
{image}
</View>
}
</LinearGradient>
</View>
)
},
Header: ({children}) => {
return (
<View style={{marginBottom: 20}}>
{children}
</View>
)
},
Body: ({children}) => {
return (
<View>
{children}
</View>
)
},
TitleHeader: ({children}) => {
return (
<>
<Text style={{fontSize: 20, fontWeight: "bold", marginBottom: 10, color: "#0F0F0F"}}>{children}</Text>
</>
)
},
TextSmall: ({children, style, ...props}) => {
return (
<>
<Text style={{
fontSize: 16,
fontWeight: "bold",
// marginBottom: 10,
color: "rgba(16,17,17,0.4)",
...style
}} {...props}>{children}</Text>
</>
)
},
TitleExtra: ({children}) => {
return (
<>
<Text style={{
fontSize: 32,
fontWeight: "bold",
marginBottom: 10,
color: "rgba(16,17,17,0.4)"
}}>{children}</Text>
</>
)
}
}
export default Card

158
components/task.js Normal file
View File

@ -0,0 +1,158 @@
import Card from "./card";
import {useEffect, useState} from "react";
import {Pressable, Image, Linking} from "react-native";
// Icons
import TickActive from '../assets/ph_check-fill.svg'
import TickDanger from '../assets/tick_danger.svg'
import TickSuccess from '../assets/tick_success.svg'
import Chevron from '../assets/bi_chevron.svg'
const Task = ({id, status, title, description, timeTill, navigation, timeFinished, imageType, navi}) => {
const [leastTime, setLeastTime] = useState("");
const variantSelection = () => {
switch (status) {
case "active":
if (new Date().getTime() <= new Date(timeTill)) {
return "default"
}
return "error"
case "completed":
if (new Date(timeFinished).getTime() <= new Date(timeTill).getTime()) {
return "success"
}
return "danger"
}
}
const countDays = () => {
const date = new Date(timeTill);
let now = !!timeFinished ? new Date(timeFinished) : new Date();
const diffTime = Math.abs(date - now);
return Math.floor(diffTime / (1000 * 60 * 60 * 24));
}
const countHours = () => {
const date = new Date(timeTill);
let now = !!timeFinished ? new Date(timeFinished) : new Date();
if (now.getTime() <= date.getTime()) {
now = now.getTime() + countDays() * 24 * 60 * 60 * 1000;
} else {
now = now.getTime() - countDays() * 24 * 60 * 60 * 1000;
}
const diffTime = Math.abs(date - now);
return Math.floor(diffTime / (1000 * 60 * 60));
}
const countMinutes = () => {
const date = new Date(timeTill);
let now = !!timeFinished ? new Date(timeFinished) : new Date();
if (now.getTime() <= date.getTime()) {
now = now.getTime() + countDays() * 24 * 60 * 60 * 1000 + countHours() * 60 * 60 * 1000;
} else {
now = now.getTime() - countDays() * 24 * 60 * 60 * 1000 - countHours() * 60 * 60 * 1000;
}
const diffTime = Math.abs(date - now);
return Math.floor(diffTime / (1000 * 60));
}
const absTime = () => {
const query = {
start: (d, idx) => {
const val = +(d.split('').slice(-2).join(''))
if (val > 10 && val < 20) return [`${d} дней`, `${d} часов`, `${d} минут`][idx]
return query['1'](d, idx)
}
,
'1': (d, idx) => {
const val = +(d.split('').slice(-1))
if (val === 1) return [`${d} день`, `${d} час`, `${d} минута`][idx]
return query['2-4'](d, idx)
},
'2-4': (d, idx) => {
const val = +(d.split('').slice(-1))
if (val > 1 && val < 5) return [`${d} дня`, `${d} часа`, `${d} минуты`][idx]
return [`${d} дней`, `${d} часов`, `${d} минут`][idx]
},
}
let days = query.start(`${countDays()}`, 0)
const hours = query.start(`${countHours()}`, 1)
const minutes = query.start(`${countMinutes()}`, 2)
setLeastTime(`${days} ${hours} ${minutes}`)
}
useEffect(() => {
absTime()
if (status !== 'active') return;
setInterval(() => absTime(), 1000)
}, []);
const SelectImage = () => {
if (imageType === 'chevron') {
return <Chevron style={{transform: [{rotate: '270deg'}]}}/>
}
switch (status) {
case "active":
return <TickActive />
case "completed":
if (new Date(timeFinished).getTime() <= new Date(timeTill).getTime()) {
return <TickSuccess />
}
return <TickDanger />
}
}
return (
<Pressable onPress={() => {
navigation.navigate('Event', {id: id})
}}>
<Card.Block variant={variantSelection()}
image={<SelectImage />}>
<Card.Header>
<Card.TitleHeader>{title}</Card.TitleHeader>
</Card.Header>
<Card.Body>
{!!description && <Card.TextSmall>{description}</Card.TextSmall>}
{!!navi && <Pressable style={{borderColor: "#ECA704", borderWidth: 2, borderRadius: 40, padding: 18, display: 'flex', flexDirection: "row", alignItems: 'center', marginBottom: 10}} onPress={() => Linking.openURL(`yandexnavi://build_route_on_map?lat_to=${navi.lat}&lon_to=${navi.lon}`)}>
<Image source={require('../assets/yandexNavi.png')} width={25} height={25} style={{marginRight: 10}}></Image>
<Card.TextSmall style={{color: "#000"}}>Открыть в яндекс картах</Card.TextSmall>
</Pressable>}
{
status === 'active' && new Date(timeTill) >= new Date() && <Card.TextSmall>Осталось времени: </Card.TextSmall>
}
{
status === 'active' && new Date(timeTill) < new Date() && <Card.TextSmall>Опоздание: </Card.TextSmall>
}
{
status === 'completed' && new Date(timeFinished) <= new Date(timeTill) &&
<Card.TextSmall>Завершено раньше на: </Card.TextSmall>
}
{
status === 'completed' && new Date(timeFinished) > new Date(timeTill) &&
<Card.TextSmall>Завершено c опозданием на: </Card.TextSmall>
}
<Card.TitleExtra>{leastTime}</Card.TitleExtra>
</Card.Body>
</Card.Block>
</Pressable>
)
}
export default Task;

19
metro.config.js Normal file
View File

@ -0,0 +1,19 @@
const { getDefaultConfig } = require("expo/metro-config");
module.exports = (() => {
const config = getDefaultConfig(__dirname);
const { transformer, resolver } = config;
config.transformer = {
...transformer,
babelTransformerPath: require.resolve("react-native-svg-transformer")
};
config.resolver = {
...resolver,
assetExts: resolver.assetExts.filter((ext) => ext !== "svg"),
sourceExts: [...resolver.sourceExts, "svg"]
};
return config;
})();

View File

@ -9,13 +9,26 @@
"web": "expo start --web" "web": "expo start --web"
}, },
"dependencies": { "dependencies": {
"@expo/webpack-config": "^19.0.0",
"@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.17",
"@react-navigation/stack": "^6.3.20",
"expo": "~49.0.15", "expo": "~49.0.15",
"expo-linear-gradient": "~12.3.0",
"expo-status-bar": "~1.6.0", "expo-status-bar": "~1.6.0",
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.72.6" "react-dom": "18.2.0",
"react-native": "0.72.6",
"react-native-gesture-handler": "~2.12.0",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0",
"react-native-svg": "13.9.0",
"react-native-web": "~0.19.6"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0" "@babel/core": "^7.20.0",
"react-native-svg-transformer": "^1.2.0"
}, },
"private": true "private": true
} }

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

56
screens/event.js Normal file
View File

@ -0,0 +1,56 @@
import {ScrollView, Text} from "react-native";
import Accordion from "../components/acordion";
import Task from "../components/task";
const Event = ({route, navigation}) => {
const events = [
{
id: "1",
title: "Прибыть на ПГР 20.12.2023 к 10:00 ",
timeFinished: "2023-12-5T12:00:00.000Z",
timeTill: "2023-12-5T12:10:00.000Z",
status: "completed"
},
{
id: "2",
title: "Поставить ТС к доку №5 ",
timeTill: "2023-12-5T12:10:00.000Z",
status: "active"
},
{
id: "3",
title: "Закончить ПГР",
timeTill: "2023-12-14T12:10:00.000Z",
status: "active"
},
{
id: "4",
title: "Прибыть на ПГР 21.12.2023 к 10:00 ",
timeTill: "2023-12-21T12:10:00.000Z",
status: "active",
navi: {
lat: "55.784444",
lon: "37.711261"
}
}
]
return (
<>
<Text style={{fontSize: 48, fontWeight: 'bold', paddingLeft: 20, paddingTop: 60}}>События</Text>
<ScrollView style={{padding: 20}}>
<Accordion title={"Активные События"} style={{marginBottom: 20}}>
{events.filter(elem => elem.status === 'active').map(elem => <Task navigation={navigation} {...elem} />)}
</Accordion>
<Accordion title={"Архивные События"}>
{events.filter(elem => elem.status === 'completed').map(elem => <Task navigation={navigation} {...elem} />)}
</Accordion>
</ScrollView>
</>
)
}
export default Event;

47
screens/home.js Normal file
View File

@ -0,0 +1,47 @@
import {ScrollView, Text} from "react-native";
import Accordion from "../components/acordion";
import Task from "../components/task";
import {useId} from "react";
const Home = ({navigation}) => {
const tasks = [
{id: "1",
title: "Автомобиль назначен на маршрут Москва-Омск-Новосибирск",
status: "active",
timeTill: "2023-12-10T23:59:59.000Z"},
{id: "2",
title: "Автомобиль назначен на маршрут Москва-Омск-Новосибирск",
status: "active",
timeTill: "2024-12-31T23:59:59.000Z"},
{id: "3",
title: "Автомобиль назначен на маршрут Москва-Омск-Новосибирск",
status: "completed",
timeTill: "2023-12-31T23:59:59.000Z",
timeFinished: "2023-12-31T23:58:59.000Z",
},
{id: "4",
title: "Автомобиль назначен на маршрут Москва-Омск-Новосибирск",
status: "completed",
timeTill: "2023-12-05T23:59:59.000Z",
timeFinished: "2023-12-06T23:58:59.000Z",
},
]
return (
<>
<Text style={{fontSize: 48, fontWeight: 'bold', paddingLeft: 20, paddingTop: 60}}>Задачи</Text>
<ScrollView style={{padding: 20}}>
<Accordion title={"Активные задания"} style={{marginBottom: 20}}>
{tasks.filter(elem => elem.status === 'active').map(elem => <Task navigation={navigation} imageType={'chevron'} {...elem} key={elem.id} />)}
</Accordion>
<Accordion title={"Архивные задания"}>
{tasks.filter(elem => elem.status === 'completed').map(elem => <Task navigation={navigation} imageType={'chevron'} {...elem} key={elem.id} />)}
</Accordion>
</ScrollView>
</>
)
}
export default Home;

16
screens/login.js Normal file
View File

@ -0,0 +1,16 @@
import {Button, Text, View} from "react-native";
const Login = ({navigation}) => {
return (
<>
<View>
<Text>Login</Text>
<Button onPress={() => {
navigation.navigate("Home")
}} title={"Login"} />
</View>
</>
)
}
export default Login;

3
styles.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

8
tailwind.config.js Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./App.js"],
theme: {
extend: {},
},
plugins: [],
};

9034
yarn.lock Normal file

File diff suppressed because it is too large Load Diff