From 85d45e009b2ae6dfa99d4b9753a535780695964e Mon Sep 17 00:00:00 2001 From: Ernest Litvinenko Date: Wed, 13 Dec 2023 12:20:39 +0300 Subject: [PATCH] Initial application --- .gitignore | 2 + App.js | 92 +- assets/bi_chevron.svg | 3 + assets/notificationActive.svg | 3 + assets/notificationInactive.svg | 3 + assets/ph_check-fill.svg | 10 + assets/taskIconActive.svg | 11 + assets/taskIconInactive.svg | 11 + assets/tick_danger.svg | 10 + assets/tick_success.svg | 10 + assets/yandexNavi.png | Bin 0 -> 837 bytes components/acordion.js | 27 + components/card.js | 99 + components/task.js | 158 + metro.config.js | 19 + package.json | 17 +- postcss.config.js | 6 + screens/event.js | 56 + screens/home.js | 47 + screens/login.js | 16 + styles.css | 3 + tailwind.config.js | 8 + yarn.lock | 9034 +++++++++++++++++++++++++++++++ 23 files changed, 9629 insertions(+), 16 deletions(-) create mode 100644 assets/bi_chevron.svg create mode 100644 assets/notificationActive.svg create mode 100644 assets/notificationInactive.svg create mode 100644 assets/ph_check-fill.svg create mode 100644 assets/taskIconActive.svg create mode 100644 assets/taskIconInactive.svg create mode 100644 assets/tick_danger.svg create mode 100644 assets/tick_success.svg create mode 100644 assets/yandexNavi.png create mode 100644 components/acordion.js create mode 100644 components/card.js create mode 100644 components/task.js create mode 100644 metro.config.js create mode 100644 postcss.config.js create mode 100644 screens/event.js create mode 100644 screens/home.js create mode 100644 screens/login.js create mode 100644 styles.css create mode 100644 tailwind.config.js create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 05647d5..99be6e9 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ yarn-error.* # typescript *.tsbuildinfo + +.idea/ \ No newline at end of file diff --git a/App.js b/App.js index 09f879b..e9197c2 100644 --- a/App.js +++ b/App.js @@ -1,20 +1,84 @@ -import { StatusBar } from 'expo-status-bar'; -import { StyleSheet, Text, View } from 'react-native'; +import {StyleSheet, 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: , + default: + }, + + notificationIcon: { + focused: , + default: + } + } + + return !!focused ? styles[style].focused : styles[style].default + +} + +const Stack = createStackNavigator(); +const Tab = createBottomTabNavigator(); + + +const HomeStack = () => ( + + + + + + +) export default function App() { - return ( - - Open up App.js to start working on your app! - - - ); + + return ( + + + + generateTabBarIcon({focused, style: "taskIcon"}) + }}/> + } + options={ + { + tabBarLabel: "Уведомления", + tabBarIcon: ({focused}) => generateTabBarIcon({ + focused, + style: "notificationIcon" + }) + } + } + /> + + + + + ); } const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - alignItems: 'center', - justifyContent: 'center', - }, + container: { + flex: 1, + backgroundColor: '#fff', + }, }); diff --git a/assets/bi_chevron.svg b/assets/bi_chevron.svg new file mode 100644 index 0000000..d57a085 --- /dev/null +++ b/assets/bi_chevron.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/notificationActive.svg b/assets/notificationActive.svg new file mode 100644 index 0000000..440e85a --- /dev/null +++ b/assets/notificationActive.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/notificationInactive.svg b/assets/notificationInactive.svg new file mode 100644 index 0000000..7f9b1a2 --- /dev/null +++ b/assets/notificationInactive.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/ph_check-fill.svg b/assets/ph_check-fill.svg new file mode 100644 index 0000000..466b061 --- /dev/null +++ b/assets/ph_check-fill.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/taskIconActive.svg b/assets/taskIconActive.svg new file mode 100644 index 0000000..881a45e --- /dev/null +++ b/assets/taskIconActive.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/taskIconInactive.svg b/assets/taskIconInactive.svg new file mode 100644 index 0000000..4691388 --- /dev/null +++ b/assets/taskIconInactive.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/tick_danger.svg b/assets/tick_danger.svg new file mode 100644 index 0000000..f470d0e --- /dev/null +++ b/assets/tick_danger.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/tick_success.svg b/assets/tick_success.svg new file mode 100644 index 0000000..4d385fa --- /dev/null +++ b/assets/tick_success.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/yandexNavi.png b/assets/yandexNavi.png new file mode 100644 index 0000000000000000000000000000000000000000..df3d871686ce9ae002cfb600530fab40e62081d9 GIT binary patch literal 837 zcmV-L1G@Z)P)P000^Y1^@s6LVfqm00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yPeoC-W{lj`tvvmVLk$hi?FkeSRMiFwR0x+36;7ZhiMLg=q59sb`NC^_%{kT ztKXp zxNF2hFN|12oP^fow&;O+mPF;5+d2!#&9lJTRskM3wIJa?GNWy&SIbkO4bJc(nc|cY zjWMqQBUaDvRTv^#$2P((W*ez5Rif5`{MSkW7veMAR`_*&SQ$62NJ0IA&=C;L7mPsR z7#faR2-R&M5C#%-fv@j&A|C!uhR+(L9tfzOpy&0?4x&e#pe$yoMO7pyf=Eu>+E{4r zF7_HQ6AoyUQ?dNd?QM#HBSwObtNK8ur=lk%$#H(MFX?@xQK8|uUCvZ7a1R1ipsCwh zVv;A`UKRp!bIghD;;RzTICewMvH7Xj$_{Wak^2}vQ?2iR=$ikPg3F@+ P00000NkvXXu0mjf73_I& literal 0 HcmV?d00001 diff --git a/components/acordion.js b/components/acordion.js new file mode 100644 index 0000000..d38d07d --- /dev/null +++ b/components/acordion.js @@ -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 ( + + setActive(!active)}> + + {title} + + {/**/} + + + + + {children} + + + ) +} + +export default Accordion; \ No newline at end of file diff --git a/components/card.js b/components/card.js new file mode 100644 index 0000000..f740e69 --- /dev/null +++ b/components/card.js @@ -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 ( + + + + {children} + + + {!!image && + + {image} + + } + + + + + + ) + + }, + + Header: ({children}) => { + return ( + + {children} + + ) + }, + + Body: ({children}) => { + return ( + + {children} + + ) + }, + + TitleHeader: ({children}) => { + return ( + <> + {children} + + ) + }, + + TextSmall: ({children, style, ...props}) => { + return ( + <> + {children} + + + ) + + }, + + TitleExtra: ({children}) => { + return ( + <> + {children} + + ) + } + +} + +export default Card \ No newline at end of file diff --git a/components/task.js b/components/task.js new file mode 100644 index 0000000..29a60bd --- /dev/null +++ b/components/task.js @@ -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 + } + + switch (status) { + case "active": + return + case "completed": + if (new Date(timeFinished).getTime() <= new Date(timeTill).getTime()) { + return + } + return + } + } + + return ( + { + navigation.navigate('Event', {id: id}) + }}> + }> + + {title} + + + + {!!description && {description}} + {!!navi && Linking.openURL(`yandexnavi://build_route_on_map?lat_to=${navi.lat}&lon_to=${navi.lon}`)}> + + Открыть в яндекс картах + } + + { + status === 'active' && new Date(timeTill) >= new Date() && Осталось времени: + } + + { + status === 'active' && new Date(timeTill) < new Date() && Опоздание: + } + + { + status === 'completed' && new Date(timeFinished) <= new Date(timeTill) && + Завершено раньше на: + } + { + status === 'completed' && new Date(timeFinished) > new Date(timeTill) && + Завершено c опозданием на: + } + {leastTime} + + + + ) +} + + +export default Task; \ No newline at end of file diff --git a/metro.config.js b/metro.config.js new file mode 100644 index 0000000..147731d --- /dev/null +++ b/metro.config.js @@ -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; +})(); \ No newline at end of file diff --git a/package.json b/package.json index 82b02c4..732fb40 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,26 @@ "web": "expo start --web" }, "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-linear-gradient": "~12.3.0", "expo-status-bar": "~1.6.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": { - "@babel/core": "^7.20.0" + "@babel/core": "^7.20.0", + "react-native-svg-transformer": "^1.2.0" }, "private": true } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..63889e7 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; \ No newline at end of file diff --git a/screens/event.js b/screens/event.js new file mode 100644 index 0000000..02c53ed --- /dev/null +++ b/screens/event.js @@ -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 ( + <> + События + + + {events.filter(elem => elem.status === 'active').map(elem => )} + + + {events.filter(elem => elem.status === 'completed').map(elem => )} + + + + ) +} + +export default Event; \ No newline at end of file diff --git a/screens/home.js b/screens/home.js new file mode 100644 index 0000000..2138fcc --- /dev/null +++ b/screens/home.js @@ -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 ( + <> + Задачи + + + {tasks.filter(elem => elem.status === 'active').map(elem => )} + + + {tasks.filter(elem => elem.status === 'completed').map(elem => )} + + + + ) +} + +export default Home; \ No newline at end of file diff --git a/screens/login.js b/screens/login.js new file mode 100644 index 0000000..b12d1c5 --- /dev/null +++ b/screens/login.js @@ -0,0 +1,16 @@ +import {Button, Text, View} from "react-native"; + +const Login = ({navigation}) => { + return ( + <> + + Login +