├── .gitattributes ├── app.json ├── src ├── consts │ ├── AsyncStorageKey.ts │ └── ScreenParameter.ts ├── lib │ ├── models │ │ ├── common │ │ │ ├── Lookup.ts │ │ │ ├── identifiable-model.ts │ │ │ ├── time-loggable-model.ts │ │ │ ├── project-limited-model.ts │ │ │ ├── time.ts │ │ │ └── error-able-response.ts │ │ ├── task │ │ │ ├── task-timeline.ts │ │ │ ├── task-list.ts │ │ │ ├── task-status.ts │ │ │ ├── task-time-log.ts │ │ │ └── task.ts │ │ ├── auth │ │ │ └── login.ts │ │ ├── team │ │ │ ├── team-list.ts │ │ │ └── team-create.ts │ │ └── user │ │ │ └── user.ts │ ├── components │ │ ├── dialogs │ │ │ └── single-option-dialog │ │ │ │ ├── styles.ts │ │ │ │ └── SingleOptionDialog.tsx │ │ ├── form-controls │ │ │ ├── form-picker │ │ │ │ ├── utils │ │ │ │ │ └── form-picker-item.ts │ │ │ │ ├── styles.ts │ │ │ │ └── FormPickerControl.tsx │ │ │ └── form-date-picker │ │ │ │ ├── styles.ts │ │ │ │ └── FormDatePickerControl.tsx │ │ ├── snack-notification │ │ │ ├── styles.ts │ │ │ └── SnackNotification.tsx │ │ ├── icons │ │ │ ├── initials-based-avatar │ │ │ │ ├── styles.ts │ │ │ │ └── InitialsBasedAvatar.tsx │ │ │ └── navigation-icon │ │ │ │ ├── styles.ts │ │ │ │ └── NavigationIcon.tsx │ │ ├── headers │ │ │ ├── search-header │ │ │ │ ├── search-bar-wrapper │ │ │ │ │ ├── styles.ts │ │ │ │ │ └── SearchBarWrapper.tsx │ │ │ │ ├── styles.ts │ │ │ │ └── SearchHeader.tsx │ │ │ └── screen-header │ │ │ │ ├── styles.ts │ │ │ │ └── ScreenHeader.tsx │ │ └── central-spinner │ │ │ ├── styles.ts │ │ │ └── CentralSpinner.tsx │ └── network │ │ ├── common │ │ └── http-error.ts │ │ ├── api │ │ ├── environment-properties.ts │ │ └── endpoints-constant.ts │ │ └── http-services │ │ ├── auth-service.ts │ │ ├── common │ │ └── base-http-service.ts │ │ ├── team-service.ts │ │ ├── user-service.ts │ │ └── task-service.ts ├── assets │ ├── fonts │ │ ├── font.ts │ │ └── Sniglet │ │ │ ├── Sniglet-Regular.ttf │ │ │ └── Sniglet-ExtraBold.ttf │ ├── images │ │ ├── readme │ │ │ ├── login.png │ │ │ ├── addTask.png │ │ │ ├── addTeam.png │ │ │ ├── loading.png │ │ │ ├── newUser.png │ │ │ ├── sideBar.png │ │ │ ├── taskFlow.png │ │ │ ├── taskList.png │ │ │ ├── taskLog.png │ │ │ ├── teamList.png │ │ │ ├── userList.png │ │ │ ├── taskDetails.png │ │ │ ├── taskStatus.png │ │ │ ├── userDetails.png │ │ │ └── userProfile.png │ │ ├── react_cloud_icon │ │ │ ├── icons8-react-native-filled-50.png │ │ │ ├── icons8-react-native-filled-100.png │ │ │ ├── icons8-react-native-filled-150.png │ │ │ ├── icons8-react-native-filled-200.png │ │ │ ├── icons8-react-native-filled-250.png │ │ │ ├── icons8-react-native-filled-300.png │ │ │ ├── icons8-react-native-filled-350.png │ │ │ ├── icons8-react-native-filled-400.png │ │ │ ├── icons8-react-native-filled-450.png │ │ │ └── icons8-react-native-filled-500.png │ │ └── react_dark_icon │ │ │ ├── icons8-react-native-filled-100.png │ │ │ ├── icons8-react-native-filled-150.png │ │ │ ├── icons8-react-native-filled-200.png │ │ │ ├── icons8-react-native-filled-250.png │ │ │ ├── icons8-react-native-filled-300.png │ │ │ ├── icons8-react-native-filled-350.png │ │ │ ├── icons8-react-native-filled-400.png │ │ │ ├── icons8-react-native-filled-450.png │ │ │ ├── icons8-react-native-filled-50.png │ │ │ └── icons8-react-native-filled-500.png │ ├── color.ts │ └── paper-theme.ts ├── screens │ ├── auth-loading │ │ ├── styles.ts │ │ └── AuthLoadingScreen.tsx │ ├── user-list │ │ ├── user-list-chips │ │ │ ├── styles.ts │ │ │ └── UserListChips.tsx │ │ ├── styles.ts │ │ └── UserListScreen.tsx │ ├── team-list │ │ ├── styles.ts │ │ └── TeamListScreen.tsx │ ├── task-status-flow │ │ ├── styles.ts │ │ └── TaskStatusFlowScreen.tsx │ ├── login │ │ ├── styles.ts │ │ └── LoginScreen.tsx │ ├── create-user │ │ ├── styles.ts │ │ └── CreateUserScreen.tsx │ ├── create-task │ │ ├── styles.ts │ │ └── CreateTaskScreen.tsx │ ├── user-details │ │ ├── styles.ts │ │ └── UserDetailsScreen.tsx │ ├── create-team │ │ ├── styles.ts │ │ └── CreateTeamScreen.tsx │ ├── task-time-log │ │ ├── styles.ts │ │ └── TaskTimeLogScreen.tsx │ ├── task-list │ │ ├── styles.ts │ │ └── TaskListScreen.tsx │ └── task-details │ │ ├── styles.ts │ │ └── TaskDetailsScreen.tsx └── navigation │ ├── AppAuthSwitchNavigator.tsx │ ├── TeamsStackNavigator.ts │ ├── UsersStackNavigator.ts │ ├── routes.ts │ ├── TasksStackNavigator.ts │ ├── UsersDrawerNavigator.tsx │ └── AppBottomTabNavigator.tsx ├── babel.config.js ├── ios ├── LeadAnthill │ ├── Images.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.h │ ├── main.m │ ├── AppDelegate.m │ ├── Info.plist │ └── Base.lproj │ │ └── LaunchScreen.xib ├── LeadAnthillTests │ ├── Info.plist │ └── LeadAnthillTests.m ├── LeadAnthill-tvOSTests │ └── Info.plist ├── LeadAnthill-tvOS │ └── Info.plist └── LeadAnthill.xcodeproj │ └── xcshareddata │ └── xcschemes │ ├── LeadAnthill.xcscheme │ └── LeadAnthill-tvOS.xcscheme ├── App.tsx ├── jest.config.js ├── .buckconfig ├── .gitignore ├── metro.config.js ├── __tests__ └── App-test.tsx ├── index.js ├── tsconfig.json ├── package.json └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LeadAnthill", 3 | "displayName": "LeadAnthill" 4 | } 5 | -------------------------------------------------------------------------------- /src/consts/AsyncStorageKey.ts: -------------------------------------------------------------------------------- 1 | export enum AsyncStorageKey { 2 | JWT_TOKEN = 'JWT_TOKEN' 3 | } 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /src/lib/models/common/Lookup.ts: -------------------------------------------------------------------------------- 1 | export interface Lookup { 2 | id: number; 3 | name: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/lib/models/common/identifiable-model.ts: -------------------------------------------------------------------------------- 1 | export interface IdentifiableModel { 2 | id: number; 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/models/common/time-loggable-model.ts: -------------------------------------------------------------------------------- 1 | export interface TimeLoggableModel { 2 | timeLogged: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/models/common/project-limited-model.ts: -------------------------------------------------------------------------------- 1 | export interface ProjectLimitedModel { 2 | projectId: number; 3 | } 4 | -------------------------------------------------------------------------------- /ios/LeadAnthill/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import AppAuthSwitchNavigator from './src/navigation/AppAuthSwitchNavigator'; 2 | 3 | export default AppAuthSwitchNavigator 4 | -------------------------------------------------------------------------------- /src/assets/fonts/font.ts: -------------------------------------------------------------------------------- 1 | export enum Font { 2 | SNIGLET = 'Sniglet-Regular', 3 | SNIGLET_EXTRA_BOLD = 'Sniglet-ExtraBold' 4 | } 5 | -------------------------------------------------------------------------------- /src/lib/models/common/time.ts: -------------------------------------------------------------------------------- 1 | export interface TimeStamp { 2 | hours: number; 3 | minutes: number; 4 | seconds: number; 5 | } 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 4 | }; 5 | -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /src/assets/images/readme/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/login.png -------------------------------------------------------------------------------- /src/assets/images/readme/addTask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/addTask.png -------------------------------------------------------------------------------- /src/assets/images/readme/addTeam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/addTeam.png -------------------------------------------------------------------------------- /src/assets/images/readme/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/loading.png -------------------------------------------------------------------------------- /src/assets/images/readme/newUser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/newUser.png -------------------------------------------------------------------------------- /src/assets/images/readme/sideBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/sideBar.png -------------------------------------------------------------------------------- /src/assets/images/readme/taskFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/taskFlow.png -------------------------------------------------------------------------------- /src/assets/images/readme/taskList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/taskList.png -------------------------------------------------------------------------------- /src/assets/images/readme/taskLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/taskLog.png -------------------------------------------------------------------------------- /src/assets/images/readme/teamList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/teamList.png -------------------------------------------------------------------------------- /src/assets/images/readme/userList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/userList.png -------------------------------------------------------------------------------- /src/lib/components/dialogs/single-option-dialog/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | }) 5 | -------------------------------------------------------------------------------- /src/lib/components/form-controls/form-picker/utils/form-picker-item.ts: -------------------------------------------------------------------------------- 1 | export interface FormPickerItem { 2 | label: string; 3 | value: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/images/readme/taskDetails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/taskDetails.png -------------------------------------------------------------------------------- /src/assets/images/readme/taskStatus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/taskStatus.png -------------------------------------------------------------------------------- /src/assets/images/readme/userDetails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/userDetails.png -------------------------------------------------------------------------------- /src/assets/images/readme/userProfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/readme/userProfile.png -------------------------------------------------------------------------------- /src/assets/fonts/Sniglet/Sniglet-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/fonts/Sniglet/Sniglet-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Sniglet/Sniglet-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/fonts/Sniglet/Sniglet-ExtraBold.ttf -------------------------------------------------------------------------------- /src/lib/components/snack-notification/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | snackBar: { 5 | 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### IDE ### 2 | .idea/ 3 | 4 | ### Dependencies ### 5 | node_modules/ 6 | 7 | ### Android ### 8 | /android/ 9 | 10 | ### Watchman ### 11 | .watchmanconfig 12 | -------------------------------------------------------------------------------- /src/lib/components/icons/initials-based-avatar/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /src/lib/network/common/http-error.ts: -------------------------------------------------------------------------------- 1 | export interface HttpError { 2 | code: number; 3 | message: string; 4 | name: string; 5 | previous: object; 6 | status: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/components/icons/navigation-icon/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | width: 30 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /src/lib/models/common/error-able-response.ts: -------------------------------------------------------------------------------- 1 | export interface ErrorAbleResponse { 2 | error: HttpResponseError; 3 | } 4 | 5 | interface HttpResponseError { 6 | status: number; 7 | message: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/images/react_cloud_icon/icons8-react-native-filled-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_cloud_icon/icons8-react-native-filled-50.png -------------------------------------------------------------------------------- /src/assets/images/react_dark_icon/icons8-react-native-filled-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_dark_icon/icons8-react-native-filled-100.png -------------------------------------------------------------------------------- /src/assets/images/react_dark_icon/icons8-react-native-filled-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_dark_icon/icons8-react-native-filled-150.png -------------------------------------------------------------------------------- /src/assets/images/react_dark_icon/icons8-react-native-filled-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_dark_icon/icons8-react-native-filled-200.png -------------------------------------------------------------------------------- /src/assets/images/react_dark_icon/icons8-react-native-filled-250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_dark_icon/icons8-react-native-filled-250.png -------------------------------------------------------------------------------- /src/assets/images/react_dark_icon/icons8-react-native-filled-300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_dark_icon/icons8-react-native-filled-300.png -------------------------------------------------------------------------------- /src/assets/images/react_dark_icon/icons8-react-native-filled-350.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_dark_icon/icons8-react-native-filled-350.png -------------------------------------------------------------------------------- /src/assets/images/react_dark_icon/icons8-react-native-filled-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_dark_icon/icons8-react-native-filled-400.png -------------------------------------------------------------------------------- /src/assets/images/react_dark_icon/icons8-react-native-filled-450.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_dark_icon/icons8-react-native-filled-450.png -------------------------------------------------------------------------------- /src/assets/images/react_dark_icon/icons8-react-native-filled-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_dark_icon/icons8-react-native-filled-50.png -------------------------------------------------------------------------------- /src/assets/images/react_dark_icon/icons8-react-native-filled-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_dark_icon/icons8-react-native-filled-500.png -------------------------------------------------------------------------------- /src/assets/images/react_cloud_icon/icons8-react-native-filled-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_cloud_icon/icons8-react-native-filled-100.png -------------------------------------------------------------------------------- /src/assets/images/react_cloud_icon/icons8-react-native-filled-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_cloud_icon/icons8-react-native-filled-150.png -------------------------------------------------------------------------------- /src/assets/images/react_cloud_icon/icons8-react-native-filled-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_cloud_icon/icons8-react-native-filled-200.png -------------------------------------------------------------------------------- /src/assets/images/react_cloud_icon/icons8-react-native-filled-250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_cloud_icon/icons8-react-native-filled-250.png -------------------------------------------------------------------------------- /src/assets/images/react_cloud_icon/icons8-react-native-filled-300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_cloud_icon/icons8-react-native-filled-300.png -------------------------------------------------------------------------------- /src/assets/images/react_cloud_icon/icons8-react-native-filled-350.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_cloud_icon/icons8-react-native-filled-350.png -------------------------------------------------------------------------------- /src/assets/images/react_cloud_icon/icons8-react-native-filled-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_cloud_icon/icons8-react-native-filled-400.png -------------------------------------------------------------------------------- /src/assets/images/react_cloud_icon/icons8-react-native-filled-450.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_cloud_icon/icons8-react-native-filled-450.png -------------------------------------------------------------------------------- /src/assets/images/react_cloud_icon/icons8-react-native-filled-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene2owl/react-native-task-management-system/HEAD/src/assets/images/react_cloud_icon/icons8-react-native-filled-500.png -------------------------------------------------------------------------------- /src/consts/ScreenParameter.ts: -------------------------------------------------------------------------------- 1 | import { Dimensions } from "react-native"; 2 | 3 | const window = Dimensions.get('window'); 4 | 5 | export enum ScreenParameter { 6 | WIDTH = window.width, 7 | HEIGHT = window.height 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/components/headers/search-header/search-bar-wrapper/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | searchBar: { 5 | height: '100%', 6 | elevation: 0 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /src/lib/models/task/task-timeline.ts: -------------------------------------------------------------------------------- 1 | export interface TaskTimeline { 2 | taskid: number; 3 | taskname: string; 4 | userid: number; 5 | username: string; 6 | datecreated: string; 7 | timecreated: string; 8 | status: string; 9 | } 10 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transformer: { 3 | getTransformOptions: async () => ({ 4 | transform: { 5 | experimentalImportSupport: false, 6 | inlineRequires: false, 7 | }, 8 | }), 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /src/lib/models/auth/login.ts: -------------------------------------------------------------------------------- 1 | import { ErrorAbleResponse } from "../common/error-able-response"; 2 | 3 | export interface Login { 4 | username: string; 5 | password: string; 6 | } 7 | 8 | export interface LoginResponse extends ErrorAbleResponse { 9 | token: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/color.ts: -------------------------------------------------------------------------------- 1 | export enum Color { 2 | SUN = '#CC7832', 3 | KIND = '#FFC66D', 4 | CARBON = '#1B1B1B', 5 | ASPHALT = '#2B2B2B', 6 | ENSIGN = '#3B3B3B', 7 | LIGHT = '#A9B7C6', 8 | NEAT = '#FFF', 9 | OCEAN = '#568BD8', 10 | FRANT ='#52D2FB', 11 | BLOOD = '#B00020' 12 | } 13 | -------------------------------------------------------------------------------- /src/screens/auth-loading/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | 4 | export default StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | justifyContent: 'center', 8 | backgroundColor: Color.ENSIGN 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /src/lib/models/task/task-list.ts: -------------------------------------------------------------------------------- 1 | import { IdentifiableModel } from "../common/identifiable-model"; 2 | import { Lookup } from "../common/Lookup"; 3 | 4 | export interface TaskListItem extends IdentifiableModel { 5 | name: string; 6 | status: string; 7 | assignedTo: Lookup; 8 | deadline: string; 9 | } 10 | -------------------------------------------------------------------------------- /__tests__/App-test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from '../App'; 8 | 9 | // Note: test renderer must be required after react-native. 10 | import renderer from 'react-test-renderer'; 11 | 12 | it('renders correctly', () => { 13 | renderer.create(); 14 | }); -------------------------------------------------------------------------------- /src/lib/network/api/environment-properties.ts: -------------------------------------------------------------------------------- 1 | export enum EnvironmentType { 2 | LOCAL, 3 | REMOTE 4 | } 5 | 6 | export enum ServerUrl { 7 | LOCAL = 'http://127.0.0.1:3000/api', 8 | REMOTE = 'https://eugene-api.herokuapp.com/api' 9 | } 10 | 11 | const serverUrl = (environmentType: EnvironmentType) => 12 | environmentType === EnvironmentType.REMOTE ? 13 | ServerUrl.REMOTE : ServerUrl.LOCAL; 14 | 15 | export { serverUrl } 16 | -------------------------------------------------------------------------------- /src/lib/network/http-services/auth-service.ts: -------------------------------------------------------------------------------- 1 | import { Login } from "../../models/auth/login"; 2 | import { Endpoint } from "../api/endpoints-constant"; 3 | import { BaseHttpService } from "./common/base-http-service"; 4 | 5 | class AuthService extends BaseHttpService { 6 | 7 | login = (loginRequest: Login) => this.post(Endpoint.AUTH.login, loginRequest); 8 | } 9 | 10 | const authService = new AuthService(); 11 | 12 | export { authService } 13 | -------------------------------------------------------------------------------- /ios/LeadAnthill/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (nonatomic, strong) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ios/LeadAnthill/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './App'; 3 | import { name as appName } from './app.json'; 4 | 5 | import * as React from 'react'; 6 | import { Provider as PaperProvider } from 'react-native-paper'; 7 | import { appPaperTheme } from "./src/assets/paper-theme"; 8 | 9 | const Main = () => ( 10 | 11 | 12 | 13 | ); 14 | 15 | AppRegistry.registerComponent(appName, () => Main); 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "isolatedModules": true, 7 | "jsx": "react", 8 | "lib": [ 9 | "es6" 10 | ], 11 | "moduleResolution": "node", 12 | "noEmit": true, 13 | "strict": true, 14 | "target": "esnext" 15 | }, 16 | "exclude": [ 17 | "node_modules", 18 | "babel.config.js", 19 | "metro.config.js", 20 | "jest.config.js" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/network/http-services/common/base-http-service.ts: -------------------------------------------------------------------------------- 1 | export class BaseHttpService { 2 | 3 | private readonly HEADERS = { 4 | Accept: 'application/json', 5 | 'Content-Type': 'application/json' 6 | }; 7 | 8 | get = (url: string) => fetch(url).then((response: Response)=> response.json()); 9 | 10 | post = (url: string, body: object) => fetch( 11 | url, 12 | { 13 | method: 'POST', 14 | headers: this.HEADERS, 15 | body: JSON.stringify(body), 16 | } 17 | ) 18 | .then(response => response.json()); 19 | } 20 | -------------------------------------------------------------------------------- /src/screens/user-list/user-list-chips/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../../assets/color"; 3 | 4 | export default StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | flexDirection: 'row', 8 | justifyContent: 'flex-start', 9 | marginLeft: -115 10 | }, 11 | chip: { 12 | height: 32, 13 | marginHorizontal: 2, 14 | backgroundColor: Color.ASPHALT 15 | }, 16 | toPerformChip: { 17 | width: 80, 18 | color: Color.SUN 19 | }, 20 | inProgressChip: { 21 | width: 110, 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/lib/models/task/task-status.ts: -------------------------------------------------------------------------------- 1 | export class TaskStatus { 2 | 3 | static readonly value = { 4 | TO_PERFORM: { id: 1, label: 'To Do', icon: 'description' }, 5 | IN_PROGRESS: { id: 2, label: 'In Progress', icon: 'developer-board' }, 6 | READY_FOR_VERIFICATION: { id: 3, label: 'Ready For Verification', icon: 'gamepad' }, 7 | VERIFICATION_APPROVED: { id: 4, label: 'Verification Approved', icon: 'done' }, 8 | DONE: { id: 5, label: 'Done', icon: 'done-all' }, 9 | FROZEN: { id: 6, label: 'Frozen', icon: 'waves' }, 10 | DENIED: { id: 7, label: 'Denied', icon: 'whatshot' } 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/components/icons/navigation-icon/NavigationIcon.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import { View } from "react-native"; 3 | import { Icon } from "react-native-elements"; 4 | import * as React from "react"; 5 | 6 | interface NavigationIconProps { 7 | name: string; 8 | distent: boolean; 9 | tintColor: string; 10 | } 11 | 12 | const NavigationIcon = (props: NavigationIconProps) => { 13 | return ( 14 | 15 | 16 | 17 | ) 18 | }; 19 | 20 | export { NavigationIcon } 21 | -------------------------------------------------------------------------------- /src/screens/user-list/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | 4 | export default StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | backgroundColor: Color.ENSIGN 8 | }, 9 | scrollContainer: { 10 | 11 | }, 12 | scrollContainerContent: { 13 | 14 | }, 15 | contentContainer: { 16 | 17 | }, 18 | listItem: { 19 | height: 60 20 | }, 21 | listItemRight: { 22 | paddingRight: 30, 23 | paddingTop: 10 24 | }, 25 | fab: { 26 | position: 'absolute', 27 | margin: 16, 28 | right: 0, 29 | bottom: 0, 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /src/lib/components/headers/search-header/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../../../assets/color"; 3 | import { ScreenParameter } from "../../../../consts/ScreenParameter"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | flexDirection: 'row', 8 | 9 | backgroundColor: Color.ASPHALT, 10 | height: ScreenParameter.HEIGHT / 12, 11 | 12 | // shadow 13 | shadowColor: Color.CARBON, 14 | elevation: 4 15 | }, 16 | searchBarContainer: { 17 | flex: 6 18 | }, 19 | closeIconOpacity: { 20 | flex: 1, 21 | justifyContent: 'center' 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /src/lib/models/team/team-list.ts: -------------------------------------------------------------------------------- 1 | import { ProjectLimitedModel } from "../common/project-limited-model"; 2 | import { IdentifiableModel } from "../common/identifiable-model"; 3 | import { TimeLoggableModel } from "../common/time-loggable-model"; 4 | import { ErrorAbleResponse } from "../common/error-able-response"; 5 | 6 | export interface TeamListItem extends IdentifiableModel, ProjectLimitedModel, TimeLoggableModel, ErrorAbleResponse { 7 | name: string; 8 | members: TeamListItemMember[] 9 | } 10 | 11 | export interface TeamListItemMember extends IdentifiableModel, TimeLoggableModel { 12 | username: string; 13 | isTeamLeader: boolean; 14 | role: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/components/central-spinner/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../../assets/color"; 3 | import { Font } from "../../../assets/fonts/font"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | justifyContent: 'center', 8 | alignItems: 'center', 9 | height: 0 10 | }, 11 | fullScreen: { 12 | height: '100%' 13 | }, 14 | activityIndicator: { 15 | }, 16 | messageContainer: { 17 | height: 60, 18 | justifyContent: 'center', 19 | alignItems: 'center' 20 | }, 21 | messageText: { 22 | fontSize: 15, 23 | color: Color.LIGHT, 24 | fontFamily: Font.SNIGLET 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /src/lib/network/http-services/team-service.ts: -------------------------------------------------------------------------------- 1 | import { Endpoint } from "../api/endpoints-constant"; 2 | import { BaseHttpService } from "./common/base-http-service"; 3 | import { TeamCreateRequest } from "../../models/team/team-create"; 4 | 5 | class TeamService extends BaseHttpService { 6 | 7 | getAll = (projectId: number) => this.get(Endpoint.TEAMS.baseByProjectId(projectId)); 8 | 9 | create = (createRequest: TeamCreateRequest) => this.post(Endpoint.TEAMS.create, createRequest); 10 | 11 | getPreCreateData = (projectId: number) => this.get(Endpoint.TEAMS.createByProjectId(projectId)); 12 | } 13 | 14 | const teamService = new TeamService(); 15 | 16 | export { teamService } 17 | -------------------------------------------------------------------------------- /src/lib/models/team/team-create.ts: -------------------------------------------------------------------------------- 1 | import { ProjectLimitedModel } from "../common/project-limited-model"; 2 | import { IdentifiableModel } from "../common/identifiable-model"; 3 | import { TimeLoggableModel } from "../common/time-loggable-model"; 4 | import { TeamListItemMember } from "./team-list"; 5 | import { ErrorAbleResponse } from "../common/error-able-response"; 6 | 7 | export interface TeamCreateRequest extends ProjectLimitedModel { 8 | name: string; 9 | leader: number; 10 | members: number[]; 11 | } 12 | 13 | export interface TeamCreateResponse extends IdentifiableModel, TimeLoggableModel, ProjectLimitedModel, ErrorAbleResponse { 14 | name: string; 15 | members: TeamListItemMember[]; 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/components/form-controls/form-picker/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../../../assets/color"; 3 | import { Font } from "../../../../assets/fonts/font"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | marginVertical: 7, 8 | backgroundColor: Color.ASPHALT, 9 | padding: 5, 10 | borderRadius: 15, 11 | position: 'relative' 12 | }, 13 | pickerLabel: { 14 | color: Color.OCEAN, 15 | position: 'absolute', 16 | left: 30, 17 | top: -11 18 | }, 19 | picker: { 20 | width: '100%', 21 | height: 55, 22 | backgroundColor: Color.ASPHALT, 23 | color: Color.SUN, 24 | fontFamily: Font.SNIGLET 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /src/lib/components/form-controls/form-date-picker/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../../../assets/color"; 3 | import { Font } from "../../../../assets/fonts/font"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | width: '60%', 8 | marginVertical: 7, 9 | backgroundColor: Color.ASPHALT, 10 | padding: 5, 11 | borderRadius: 15, 12 | position: 'relative' 13 | }, 14 | pickerLabel: { 15 | color: Color.OCEAN, 16 | position: 'absolute', 17 | left: 30, 18 | top: -11 19 | }, 20 | picker: { 21 | height: 55, 22 | backgroundColor: Color.ASPHALT, 23 | color: Color.SUN, 24 | fontFamily: Font.SNIGLET 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /src/lib/network/http-services/user-service.ts: -------------------------------------------------------------------------------- 1 | import { Endpoint } from "../api/endpoints-constant"; 2 | import { BaseHttpService } from "./common/base-http-service"; 3 | import { UserCreateRequest } from "../../models/user/user"; 4 | 5 | class UserService extends BaseHttpService { 6 | 7 | getAll = (projectId: number) => this.get(Endpoint.USERS.baseByProjectId(projectId)); 8 | 9 | create = (createRequest: UserCreateRequest) => this.post(Endpoint.USERS.create, createRequest); 10 | 11 | getPreCreateData = (projectId: number) => this.get(Endpoint.USERS.createByProjectId(projectId)); 12 | 13 | getDetails = (id: number) => this.get(Endpoint.USERS.details(id)); 14 | } 15 | 16 | const userService = new UserService(); 17 | 18 | export { userService } 19 | -------------------------------------------------------------------------------- /src/lib/models/task/task-time-log.ts: -------------------------------------------------------------------------------- 1 | import { IdentifiableModel } from "../common/identifiable-model"; 2 | import { TimeStamp } from "../common/time"; 3 | import { ErrorAbleResponse } from "../common/error-able-response"; 4 | 5 | export interface TaskTimelog extends IdentifiableModel, ErrorAbleResponse { 6 | name: string; 7 | total: TimeStamp; 8 | users: TaskTimelogUser[]; 9 | } 10 | 11 | export interface TaskTimelogUser extends IdentifiableModel { 12 | username: string; 13 | timelogged: string; 14 | datecreated: string | Date; 15 | timecreated: string; 16 | } 17 | 18 | export interface TaskTimelogRequest extends ErrorAbleResponse { 19 | userId: number; 20 | taskId: number; 21 | comment: string; 22 | hoursLogged: number; 23 | minutesLogged: number; 24 | } 25 | -------------------------------------------------------------------------------- /src/screens/team-list/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | import { Font } from "../../assets/fonts/font"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | flex: 1, 8 | backgroundColor: Color.ENSIGN 9 | }, 10 | scrollContainer: { 11 | 12 | }, 13 | scrollContainerContent: { 14 | 15 | }, 16 | contentContainer: { 17 | 18 | }, 19 | listItem: { 20 | height: 60 21 | }, 22 | listItemRight: { 23 | paddingRight: 30, 24 | paddingTop: 10 25 | }, 26 | listItemRightText: { 27 | fontFamily: Font.SNIGLET, 28 | color: Color.SUN 29 | }, 30 | fab: { 31 | position: 'absolute', 32 | margin: 16, 33 | right: 0, 34 | bottom: 0, 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /src/lib/components/snack-notification/SnackNotification.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from 'react'; 3 | import { Component } from 'react'; 4 | import { Snackbar } from 'react-native-paper'; 5 | 6 | interface Props { 7 | message: string; 8 | onDismiss: () => void; 9 | } 10 | 11 | export class SnackNotification extends Component { 12 | 13 | render() { 14 | const { message, onDismiss } = this.props; 15 | 16 | return ( 17 | onDismiss() } 21 | duration={ 2000 } 22 | action={ { label: 'Ok', onPress: () => onDismiss() } } 23 | > 24 | { message } 25 | 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ios/LeadAnthill/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /src/screens/task-status-flow/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | 4 | export default StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | backgroundColor: Color.ENSIGN 8 | }, 9 | scrollContainer: { 10 | 11 | }, 12 | scrollContainerContent: { 13 | 14 | }, 15 | contentContainer: { 16 | 17 | }, 18 | listItem: { 19 | height: 60 20 | }, 21 | listItemRight: { 22 | alignItems: 'flex-end', 23 | paddingRight: 10, 24 | paddingTop: 10 25 | }, 26 | listItemRightTextStatus: { 27 | color: Color.OCEAN 28 | }, 29 | listItemRightDescription: { 30 | color: Color.SUN 31 | }, 32 | fab: { 33 | position: 'absolute', 34 | margin: 16, 35 | right: 0, 36 | bottom: 0, 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /ios/LeadAnthillTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/lib/components/icons/initials-based-avatar/InitialsBasedAvatar.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import { View } from "react-native"; 3 | import { Icon } from "react-native-elements"; 4 | import * as React from "react"; 5 | import { Avatar } from "react-native-paper"; 6 | 7 | interface Props { 8 | name: string; 9 | size?: number; 10 | } 11 | 12 | function extractAvatarLabel(username: string): string { 13 | const words = username.split(' '); 14 | return words.map(word => word.charAt(0).toUpperCase()).join('').substring(0, 3); 15 | } 16 | 17 | const InitialsBasedAvatar = (props: Props) => { 18 | const { name, size } = props; 19 | 20 | return ( 21 | 22 | 23 | 24 | ) 25 | }; 26 | 27 | export { InitialsBasedAvatar } 28 | -------------------------------------------------------------------------------- /src/navigation/AppAuthSwitchNavigator.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { createAppContainer, createSwitchNavigator } from 'react-navigation'; 3 | import { AppAuthSwitch } from "./routes"; 4 | import { AuthLoadingScreen } from "../screens/auth-loading/AuthLoadingScreen"; 5 | import AppBottomTabNavigator from './AppBottomTabNavigator'; 6 | import { LoginScreen } from "../screens/login/LoginScreen"; 7 | 8 | const routeConfigs = { 9 | [AppAuthSwitch.AUTH_LOADING]: AuthLoadingScreen, 10 | [AppAuthSwitch.APP]: AppBottomTabNavigator, 11 | [AppAuthSwitch.LOGIN]: LoginScreen 12 | }; 13 | 14 | const navigatorConfig = { 15 | initialRouteName: AppAuthSwitch.AUTH_LOADING 16 | }; 17 | 18 | console.disableYellowBox = true; 19 | const appAuthSwitchNavigator = createSwitchNavigator(routeConfigs, navigatorConfig); 20 | 21 | export default createAppContainer(appAuthSwitchNavigator) 22 | -------------------------------------------------------------------------------- /src/navigation/TeamsStackNavigator.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { createAppContainer, createStackNavigator } from "react-navigation"; 3 | import { TeamsStack } from "./routes"; 4 | import { UserDetailsScreen } from "../screens/user-details/UserDetailsScreen"; 5 | import { TeamListScreen } from "../screens/team-list/TeamListScreen"; 6 | import { CreateTeamScreen } from "../screens/create-team/CreateTeamScreen"; 7 | 8 | const routerConfigs = { 9 | [TeamsStack.TEAM_LIST]: TeamListScreen, 10 | [TeamsStack.USER_DETAILS]: UserDetailsScreen, 11 | [TeamsStack.CREATE_TEAM]: CreateTeamScreen, 12 | }; 13 | 14 | const navigatorConfig = { 15 | initialRouteName: TeamsStack.TEAM_LIST, 16 | headerMode: 'none' 17 | }; 18 | 19 | const TeamsStackNavigator = createStackNavigator(routerConfigs, navigatorConfig); 20 | 21 | export default createAppContainer(TeamsStackNavigator); 22 | -------------------------------------------------------------------------------- /src/navigation/UsersStackNavigator.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { createAppContainer, createStackNavigator } from "react-navigation"; 3 | import { UsersStack } from "./routes"; 4 | import { UserListScreen } from "../screens/user-list/UserListScreen"; 5 | import { UserDetailsScreen } from "../screens/user-details/UserDetailsScreen"; 6 | import { CreateUserScreen } from "../screens/create-user/CreateUserScreen"; 7 | 8 | const routerConfigs = { 9 | [UsersStack.USER_LIST]: UserListScreen, 10 | [UsersStack.USER_DETAILS]: UserDetailsScreen, 11 | [UsersStack.CREATE_USER]: CreateUserScreen 12 | }; 13 | 14 | const navigatorConfig = { 15 | initialRouteName: UsersStack.USER_LIST, 16 | headerMode: 'none' 17 | }; 18 | 19 | const UsersStackNavigator = createStackNavigator(routerConfigs, navigatorConfig); 20 | 21 | export default createAppContainer(UsersStackNavigator); 22 | -------------------------------------------------------------------------------- /src/navigation/routes.ts: -------------------------------------------------------------------------------- 1 | export enum AppAuthSwitch { 2 | AUTH_LOADING = 'AUTH_LOADING', 3 | LOGIN = 'LOGIN', 4 | APP = 'APP' 5 | } 6 | 7 | export enum AppTab { 8 | TASKS = 'TASKS', 9 | TASK_STATUS_FLOW = 'TASK_STATUS_FLOW', 10 | USERS = 'USERS', 11 | PROFILE = 'PROFILE' 12 | } 13 | 14 | export enum TasksStack { 15 | TASK_LIST = 'TASK_LIST', 16 | TASK_DETAILS = 'TASK_DETAILS', 17 | TASK_TIME_LOG = 'TASK_TIME_LOG', 18 | CREATE_TASK = 'CREATE_TASK' 19 | } 20 | 21 | export enum UsersDrawer { 22 | USER_LIST = 'USER_LIST', 23 | TEAM_LIST = 'TEAM_LIST' 24 | } 25 | 26 | export enum UsersStack { 27 | USER_LIST = 'USER_LIST', 28 | USER_DETAILS = 'USER_DETAILS', 29 | CREATE_USER = 'CREATE_USER' 30 | } 31 | 32 | export enum TeamsStack { 33 | TEAM_LIST = 'TEAM_LIST', 34 | USER_DETAILS = 'USER_DETAILS', 35 | CREATE_TEAM = 'CREATE_TEAM' 36 | } 37 | -------------------------------------------------------------------------------- /src/screens/login/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | import { ScreenParameter } from "../../consts/ScreenParameter"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | flex: 1, 8 | backgroundColor: Color.ENSIGN 9 | }, 10 | scrollContainer: { 11 | flex: 1, 12 | width: '100%' 13 | }, 14 | scrollContainerContent: { 15 | alignItems: 'center' 16 | }, 17 | contentContainer: { 18 | flex: 1, 19 | justifyContent: 'center', 20 | width: ScreenParameter.WIDTH * 0.75 21 | }, 22 | logoContainer: { 23 | alignItems: 'center' 24 | }, 25 | formContainer: { 26 | justifyContent: 'center', 27 | }, 28 | usernameControl: { 29 | 30 | }, 31 | passwordControl: { 32 | marginVertical: 10 33 | }, 34 | submitButton: { 35 | marginBottom: 10 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /ios/LeadAnthill-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/lib/components/headers/search-header/search-bar-wrapper/SearchBarWrapper.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from 'react'; 3 | import { Searchbar } from 'react-native-paper'; 4 | import { Component } from "react"; 5 | 6 | interface Props { 7 | onIconPress: (query: string) => void; 8 | } 9 | 10 | interface State { 11 | query: string; 12 | } 13 | 14 | export default class SearchBarWrapper extends Component { 15 | 16 | state: State = { 17 | query: '' 18 | }; 19 | 20 | render() { 21 | const { onIconPress } = this.props; 22 | 23 | return ( 24 | this.setState({ query: query }) } 28 | onIconPress={ () => onIconPress(this.state.query) } 29 | style={ styles.searchBar } 30 | /> 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/screens/create-user/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | import { ScreenParameter } from "../../consts/ScreenParameter"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | flex: 1, 8 | backgroundColor: Color.ENSIGN 9 | }, 10 | scrollContainer: { 11 | flex: 1, 12 | width: '100%' 13 | }, 14 | scrollContainerContent: { 15 | alignItems: 'center' 16 | }, 17 | contentContainer: { 18 | flex: 1, 19 | width: ScreenParameter.WIDTH * 0.90 20 | }, 21 | formContainer: { 22 | }, 23 | logoContainer: { 24 | marginTop: 10, 25 | alignItems: 'center' 26 | }, 27 | usernameControl: { 28 | marginVertical: 7, 29 | }, 30 | hint: { 31 | fontSize: 13, 32 | color: Color.OCEAN 33 | }, 34 | passwordControl: { 35 | }, 36 | errorHint: { 37 | fontSize: 13, 38 | color: Color.BLOOD 39 | }, 40 | submitButton: { 41 | marginVertical: 10 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /src/lib/models/task/task.ts: -------------------------------------------------------------------------------- 1 | import { ProjectLimitedModel } from "../common/project-limited-model"; 2 | import { IdentifiableModel } from "../common/identifiable-model"; 3 | import { Lookup } from "../common/Lookup"; 4 | import { ErrorAbleResponse } from "../common/error-able-response"; 5 | 6 | export interface TaskCreateRequest extends ProjectLimitedModel { 7 | name: string; 8 | description?: string; 9 | deadline?: string; 10 | parent?: number; 11 | createdBy: number; 12 | assignedTo?: number; 13 | } 14 | 15 | export interface TaskPreCreateData extends ErrorAbleResponse { 16 | executors: Lookup[]; 17 | parents: Lookup[]; 18 | } 19 | 20 | export interface TaskDetails extends IdentifiableModel, ErrorAbleResponse { 21 | name: string; 22 | description?: string; 23 | dateCreated: string; 24 | timeCreated: string; 25 | status: string; 26 | deadline?: string; 27 | parent?: Lookup; 28 | children?: Lookup[]; 29 | createdBy: Lookup; 30 | assignedBy?: Lookup; 31 | assignedTo?: Lookup; 32 | } 33 | -------------------------------------------------------------------------------- /src/screens/create-task/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | import { ScreenParameter } from "../../consts/ScreenParameter"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | flex: 1, 8 | backgroundColor: Color.ENSIGN 9 | }, 10 | scrollContainer: { 11 | flex: 1, 12 | width: '100%' 13 | }, 14 | scrollContainerContent: { 15 | alignItems: 'center' 16 | }, 17 | contentContainer: { 18 | flex: 1, 19 | width: ScreenParameter.WIDTH * 0.90 20 | }, 21 | formContainer: { 22 | }, 23 | logoContainer: { 24 | marginTop: 10, 25 | alignItems: 'center' 26 | }, 27 | nameControl: { 28 | marginVertical: 7, 29 | }, 30 | hint: { 31 | fontSize: 13, 32 | color: Color.OCEAN 33 | }, 34 | descriptionControl: { 35 | marginVertical: 5 36 | }, 37 | formDatePickerControlContainer: { 38 | alignItems: 'center' 39 | }, 40 | submitButton: { 41 | marginVertical: 10 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /src/lib/components/central-spinner/CentralSpinner.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import { View } from "react-native"; 3 | import * as React from "react"; 4 | import { Color } from "../../../assets/color"; 5 | import { ActivityIndicator, Text } from "react-native-paper"; 6 | 7 | interface Props { 8 | animating: boolean; 9 | } 10 | 11 | const CentralSpinner = (props: Props) => { 12 | const { animating } = props; 13 | 14 | return ( 15 | 16 | 22 | { animating && 23 | 24 | Request is being processed. 25 | Please wait. 26 | 27 | } 28 | 29 | ) 30 | }; 31 | 32 | export { CentralSpinner } 33 | -------------------------------------------------------------------------------- /src/lib/components/headers/screen-header/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../../../assets/color"; 3 | import { Font } from "../../../../assets/fonts/font"; 4 | import { ScreenParameter } from "../../../../consts/ScreenParameter"; 5 | 6 | export default StyleSheet.create({ 7 | container: { 8 | flexDirection: 'row', 9 | 10 | backgroundColor: Color.CARBON, 11 | height: ScreenParameter.HEIGHT / 12, 12 | 13 | // shadow 14 | shadowColor: Color.CARBON, 15 | elevation: 4, 16 | }, 17 | leftIconOpacity: { 18 | flex: 1, 19 | justifyContent: 'center', 20 | 21 | height: '100%' 22 | }, 23 | textContainer: { 24 | flex: 5, 25 | justifyContent: 'center', 26 | 27 | height: '100%', 28 | paddingHorizontal: 5 29 | }, 30 | text: { 31 | fontSize: 35, 32 | color: Color.LIGHT, 33 | fontFamily: Font.SNIGLET, 34 | }, 35 | rightIconOpacity: { 36 | flex: 1, 37 | justifyContent: 'center', 38 | 39 | height: '100%', 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /src/assets/paper-theme.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import color from 'color'; 3 | import { DefaultTheme } from "react-native-paper"; 4 | import { Color } from "./color"; 5 | import { Font } from "./fonts/font"; 6 | 7 | const appPaperTheme = { 8 | ...DefaultTheme, 9 | dark: true, 10 | roundness: 8, 11 | colors: { 12 | ...DefaultTheme.colors, 13 | primary: Color.OCEAN, 14 | accent: Color.SUN, 15 | background: Color.ENSIGN, 16 | surface: Color.ASPHALT, 17 | error: Color.BLOOD, 18 | text: Color.LIGHT, 19 | disabled: color(Color.LIGHT) 20 | .alpha(0.26) 21 | .rgb() 22 | .string(), 23 | placeholder: color(Color.LIGHT) 24 | .alpha(0.54) 25 | .rgb() 26 | .string(), 27 | backdrop: color(Color.LIGHT) 28 | .alpha(0.5) 29 | .rgb() 30 | .string() 31 | }, 32 | fonts: { 33 | ...DefaultTheme.fonts, 34 | regular: Font.SNIGLET, 35 | medium: Font.SNIGLET, 36 | light: Font.SNIGLET, 37 | thin: Font.SNIGLET, 38 | } 39 | }; 40 | 41 | export { appPaperTheme } 42 | -------------------------------------------------------------------------------- /src/screens/user-details/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | import { Font } from "../../assets/fonts/font"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | flex: 1, 8 | backgroundColor: Color.ENSIGN 9 | }, 10 | detailsContainer: { 11 | flex: 1, 12 | paddingTop: 8 13 | }, 14 | baseDetailsZone: { 15 | flex: 1, 16 | flexDirection: 'row', 17 | alignItems: 'flex-start' 18 | }, 19 | avatarContainer: { 20 | flexDirection: 'row', 21 | padding: 18 22 | }, 23 | descriptionContainer: { 24 | flex: 1 25 | }, 26 | usernameLabel: { 27 | fontSize: 35 28 | }, 29 | roleLabel: { 30 | fontSize: 25, 31 | color: Color.SUN, 32 | marginVertical: 2 33 | }, 34 | timeLoggedLabel: { 35 | fontSize: 20 36 | }, 37 | teamsContainer: { 38 | flexDirection: 'row', 39 | flexWrap: 'wrap', 40 | paddingVertical: 2 41 | }, 42 | teamChip: { 43 | maxWidth: 100, 44 | marginVertical: 4 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /src/lib/models/user/user.ts: -------------------------------------------------------------------------------- 1 | import { IdentifiableModel } from "../common/identifiable-model"; 2 | import { Lookup } from "../common/Lookup"; 3 | import { ErrorAbleResponse } from "../common/error-able-response"; 4 | import { ProjectLimitedModel } from "../common/project-limited-model"; 5 | 6 | export interface UserCandidate extends IdentifiableModel { 7 | username: string; 8 | avatar: string; 9 | } 10 | 11 | export interface UserPreCreateData extends ErrorAbleResponse { 12 | teams: Lookup[]; 13 | roles: Lookup[]; 14 | } 15 | 16 | export interface UserCreateRequest extends ProjectLimitedModel { 17 | username: string; 18 | roleId: number; 19 | teamId: number; 20 | password: string; 21 | } 22 | 23 | export interface UserListItem extends IdentifiableModel, ErrorAbleResponse { 24 | username: string; 25 | avatar: string; 26 | tasksToPerform: number; 27 | tasksInProgress: number; 28 | } 29 | 30 | export interface UserDetails extends IdentifiableModel, ErrorAbleResponse { 31 | username: string; 32 | teams: string[]; 33 | role: string; 34 | timeLogged: string; 35 | } 36 | -------------------------------------------------------------------------------- /src/navigation/TasksStackNavigator.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { createAppContainer, createStackNavigator } from "react-navigation"; 3 | import { TasksStack, UsersStack } from "./routes"; 4 | import { TaskListScreen } from "../screens/task-list/TaskListScreen"; 5 | import { TaskDetailsScreen } from "../screens/task-details/TaskDetailsScreen"; 6 | import { TaskTimeLogScreen } from "../screens/task-time-log/TaskTimeLogScreen"; 7 | import { UserDetailsScreen } from "../screens/user-details/UserDetailsScreen"; 8 | import { CreateTaskScreen } from "../screens/create-task/CreateTaskScreen"; 9 | 10 | const routerConfigs = { 11 | [TasksStack.TASK_LIST]: TaskListScreen, 12 | [TasksStack.TASK_DETAILS]: TaskDetailsScreen, 13 | [TasksStack.CREATE_TASK]: CreateTaskScreen, 14 | [TasksStack.TASK_TIME_LOG]: TaskTimeLogScreen, 15 | [UsersStack.USER_DETAILS]: UserDetailsScreen, 16 | }; 17 | 18 | const navigatorConfig = { 19 | initialRouteName: TasksStack.TASK_LIST, 20 | headerMode: 'none' 21 | }; 22 | 23 | const TasksStackNavigator = createStackNavigator(routerConfigs, navigatorConfig); 24 | 25 | export default createAppContainer(TasksStackNavigator); 26 | -------------------------------------------------------------------------------- /src/screens/auth-loading/AuthLoadingScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { View } from "react-native"; 5 | import AsyncStorage from '@react-native-community/async-storage'; 6 | import { Color } from "../../assets/color"; 7 | import { ActivityIndicator } from "react-native-paper"; 8 | import { AppAuthSwitch } from "../../navigation/routes"; 9 | import { AsyncStorageKey } from "../../consts/AsyncStorageKey"; 10 | 11 | export class AuthLoadingScreen extends Component { 12 | 13 | // @ts-ignore 14 | private navigation = this.props.navigation; 15 | 16 | constructor(props: any) { 17 | super(props); 18 | this.loadData(); 19 | } 20 | 21 | private loadData = async () => { 22 | const jwtToken = await AsyncStorage.getItem(AsyncStorageKey.JWT_TOKEN); 23 | this.navigation.navigate(jwtToken ? AppAuthSwitch.APP : AppAuthSwitch.LOGIN); 24 | }; 25 | 26 | render(): ReactNode { 27 | return ( 28 | 29 | 30 | 31 | ); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/screens/create-team/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | import { ScreenParameter } from "../../consts/ScreenParameter"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | flex: 1, 8 | backgroundColor: Color.ENSIGN 9 | }, 10 | scrollContainer: { 11 | flex: 1, 12 | width: '100%' 13 | }, 14 | scrollContainerContent: { 15 | alignItems: 'center' 16 | }, 17 | contentContainer: { 18 | flex: 1, 19 | width: ScreenParameter.WIDTH * 0.90 20 | }, 21 | formContainer: { 22 | }, 23 | logoContainer: { 24 | alignItems: 'center' 25 | }, 26 | nameControl: { 27 | }, 28 | leaderControl: { 29 | marginVertical: 10 30 | }, 31 | candidatesControlList: { 32 | }, 33 | candidatesControlListScroll: { 34 | height: 200 35 | }, 36 | candidatesControlListScrollContent: { 37 | marginLeft: -60 38 | }, 39 | candidatesControlListItem: { 40 | height: 60 41 | }, 42 | candidatesControlListItemLeft: { 43 | }, 44 | candidatesControlListItemRight: { 45 | alignItems: 'center', 46 | paddingTop: 5 47 | }, 48 | submitButton: { 49 | marginVertical: 10 50 | } 51 | }) 52 | -------------------------------------------------------------------------------- /src/lib/components/dialogs/single-option-dialog/SingleOptionDialog.tsx: -------------------------------------------------------------------------------- 1 | import { PickerItem, SinglePickerMaterialDialog } from 'react-native-material-dialog'; 2 | import * as React from 'react'; 3 | import { Component } from 'react'; 4 | import { Color } from "../../../../assets/color"; 5 | 6 | interface Props { 7 | title: string; 8 | visible: boolean; 9 | items: PickerItem[]; 10 | selectedItem: PickerItem; 11 | onOk: (selection: PickerItem) => void; 12 | onCancel: () => void; 13 | } 14 | 15 | interface State { 16 | visible: boolean; 17 | } 18 | 19 | export class SingleOptionDialog extends Component { 20 | 21 | state: State = { 22 | visible: false 23 | }; 24 | 25 | render() { 26 | const { title, visible, items, selectedItem, onOk, onCancel } = this.props; 27 | 28 | return ( 29 | onOk(result.selectedItem) } 38 | onCancel={ () => onCancel() } 39 | /> 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/lib/components/headers/search-header/SearchHeader.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { TouchableOpacity, View } from "react-native"; 4 | import { Icon } from "react-native-elements"; 5 | import { Color } from "../../../../assets/color"; 6 | import SearchBarWrapper from "./search-bar-wrapper/SearchBarWrapper"; 7 | 8 | export interface SearchHeaderSearchIcon { 9 | onPress: (text: string) => void; 10 | } 11 | 12 | export interface SearchHeaderCloseIcon { 13 | onPress: () => void; 14 | } 15 | 16 | interface Props { 17 | searchIcon: SearchHeaderSearchIcon; 18 | closeIcon: SearchHeaderCloseIcon; 19 | } 20 | 21 | const SearchHeader = (props: Props) => { 22 | const { searchIcon, closeIcon } = props; 23 | 24 | return ( 25 | 26 | 27 | 28 | searchIcon.onPress(query) }/> 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | ) 40 | }; 41 | 42 | export { SearchHeader } 43 | -------------------------------------------------------------------------------- /src/screens/task-time-log/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | 4 | export default StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | backgroundColor: Color.ENSIGN 8 | }, 9 | logActionArea: { 10 | padding: 10, 11 | flexDirection: 'row', 12 | }, 13 | commentControl: { 14 | flex: 3 15 | }, 16 | hoursMinutesButtonArea: { 17 | flex: 2, 18 | paddingLeft: 10 19 | }, 20 | hoursMinutesArea: { 21 | flexDirection: 'row', 22 | justifyContent: 'space-between' 23 | }, 24 | hoursControl: { 25 | paddingRight: 5, 26 | width: 65 27 | }, 28 | minutesControl: { 29 | width: 80 30 | }, 31 | submitButton: { 32 | marginTop: 5 33 | }, 34 | scrollContainer: { 35 | 36 | }, 37 | scrollContainerContent: { 38 | 39 | }, 40 | contentContainer: { 41 | 42 | }, 43 | listItem: { 44 | height: 60 45 | }, 46 | listItemRight: { 47 | alignItems: 'flex-end', 48 | paddingRight: 10, 49 | paddingTop: 10 50 | }, 51 | listItemRightTextStatus: { 52 | color: Color.OCEAN 53 | }, 54 | listItemRightDescription: { 55 | color: Color.SUN 56 | }, 57 | fab: { 58 | position: 'absolute', 59 | margin: 16, 60 | right: 0, 61 | bottom: 0, 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /src/screens/task-list/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | import { Font } from "../../assets/fonts/font"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | flex: 1, 8 | backgroundColor: Color.ENSIGN 9 | }, 10 | filterPanel: { 11 | flexDirection: 'row', 12 | paddingHorizontal: 15, 13 | paddingTop: 5 14 | }, 15 | formPickerControlContainer: { 16 | width: '55%' 17 | }, 18 | switchContainer: { 19 | justifyContent: 'center', 20 | alignItems: 'center', 21 | flexDirection: 'column', 22 | paddingLeft: 40 23 | }, 24 | switchLabel: { 25 | fontSize: 16, 26 | color: Color.OCEAN 27 | }, 28 | scrollContainer: { 29 | 30 | }, 31 | scrollContainerContent: { 32 | 33 | }, 34 | contentContainer: { 35 | 36 | }, 37 | listItem: { 38 | height: 60 39 | }, 40 | listItemRight: { 41 | alignItems: 'flex-end', 42 | paddingRight: 10, 43 | paddingTop: 10 44 | }, 45 | listItemRightTextStatus: { 46 | color: Color.OCEAN 47 | }, 48 | listItemRightTextDeadline: { 49 | color: Color.SUN 50 | }, 51 | listItemRightTextDeadlineExpired: { 52 | color: Color.BLOOD 53 | }, 54 | fab: { 55 | position: 'absolute', 56 | margin: 16, 57 | right: 0, 58 | bottom: 0, 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /src/lib/network/http-services/task-service.ts: -------------------------------------------------------------------------------- 1 | import { Endpoint } from "../api/endpoints-constant"; 2 | import { BaseHttpService } from "./common/base-http-service"; 3 | import { TaskCreateRequest } from "../../models/task/task"; 4 | import { TaskTimelogRequest } from "../../models/task/task-time-log"; 5 | 6 | class TaskService extends BaseHttpService { 7 | 8 | getAll = (projectId: number, search: string, status: number, expiredOnly: boolean) => 9 | this.get(Endpoint.TASKS.baseByProjectId(projectId, search, status, expiredOnly)); 10 | 11 | create = (body: TaskCreateRequest) => this.post(Endpoint.TASKS.create, body); 12 | 13 | getPreCreateData = (projectId: number) => this.get(Endpoint.TASKS.createByProjectId(projectId)); 14 | 15 | getDetails = (id: number) => this.get(Endpoint.TASKS.details(id)); 16 | 17 | getTimeline = (search: string) => this.get(Endpoint.TASKS.timeline(search)); 18 | 19 | getTimelog = (id: number) => this.get(Endpoint.TASKS.timelog(id)); 20 | 21 | addTimelog = (id: number, body: TaskTimelogRequest) => this.post(Endpoint.TASKS.timelog(id), body); 22 | 23 | setStatus = (taskId: number, statusId: number, userId: number) => { 24 | console.log(Endpoint.TASKS.status(taskId, statusId, userId)); 25 | return this.get(Endpoint.TASKS.status(taskId, statusId, userId)); 26 | } 27 | 28 | } 29 | 30 | const taskService = new TaskService(); 31 | 32 | export { taskService } 33 | -------------------------------------------------------------------------------- /src/screens/user-list/user-list-chips/UserListChips.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import { View } from "react-native"; 3 | import * as React from "react"; 4 | import { Chip } from "react-native-paper"; 5 | import { TaskStatus } from "../../../lib/models/task/task-status"; 6 | import { appPaperTheme } from "../../../assets/paper-theme"; 7 | import { Color } from "../../../assets/color"; 8 | 9 | interface Props { 10 | tasksToPerform: number; 11 | tasksInProgress: number; 12 | } 13 | 14 | function buildChipLabel(count: number, status: string): string { 15 | return count + ' ' + status; 16 | } 17 | 18 | const UserListChips = (props: Props) => { 19 | const chipTheme = JSON.parse(JSON.stringify(appPaperTheme)); 20 | chipTheme.colors.text = Color.SUN; 21 | 22 | const { tasksToPerform, tasksInProgress } = props; 23 | 24 | return ( 25 | 26 | { !!tasksToPerform && 27 | 28 | { buildChipLabel(tasksToPerform, TaskStatus.value.TO_PERFORM.label) } 29 | 30 | } 31 | { !!tasksInProgress && 32 | 33 | { buildChipLabel(tasksInProgress, TaskStatus.value.IN_PROGRESS.label) } 34 | 35 | } 36 | 37 | ) 38 | }; 39 | 40 | export { UserListChips } 41 | -------------------------------------------------------------------------------- /ios/LeadAnthill/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | #import 11 | #import 12 | #import 13 | 14 | @implementation AppDelegate 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 19 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 20 | moduleName:@"LeadAnthill" 21 | initialProperties:nil]; 22 | 23 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 24 | 25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 26 | UIViewController *rootViewController = [UIViewController new]; 27 | rootViewController.view = rootView; 28 | self.window.rootViewController = rootViewController; 29 | [self.window makeKeyAndVisible]; 30 | return YES; 31 | } 32 | 33 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 34 | { 35 | #if DEBUG 36 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 37 | #else 38 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 39 | #endif 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /src/navigation/UsersDrawerNavigator.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { createAppContainer, createDrawerNavigator } from "react-navigation"; 3 | import { UsersDrawer } from "./routes"; 4 | import { NavigationIcon } from "../lib/components/icons/navigation-icon/NavigationIcon"; 5 | import * as React from "react"; 6 | import { Color } from "../assets/color"; 7 | import UsersStackNavigator from "./UsersStackNavigator"; 8 | import TeamsStackNavigator from "./TeamsStackNavigator"; 9 | 10 | const routerConfig = { 11 | [UsersDrawer.USER_LIST]: { 12 | screen: UsersStackNavigator, 13 | navigationOptions: { 14 | drawerLabel: 'Users', 15 | // @ts-ignore 16 | drawerIcon: ({ tintColor }) => ( 17 | 18 | ) 19 | } 20 | }, 21 | [UsersDrawer.TEAM_LIST]: { 22 | screen: TeamsStackNavigator, 23 | navigationOptions: { 24 | drawerLabel: 'Teams', 25 | // @ts-ignore 26 | drawerIcon: ({ tintColor }) => ( 27 | 28 | ) 29 | } 30 | } 31 | }; 32 | 33 | const navigatorConfig = { 34 | drawerPosition: 'right', 35 | contentOptions: { 36 | activeTintColor: Color.OCEAN, 37 | inactiveTintColor: Color.LIGHT 38 | }, 39 | drawerBackgroundColor: Color.CARBON, 40 | initialRouteName: UsersDrawer.USER_LIST, 41 | backBehavior: 'none' 42 | }; 43 | 44 | const UsersDrawerNavigator = createDrawerNavigator(routerConfig, navigatorConfig); 45 | 46 | export default createAppContainer(UsersDrawerNavigator); 47 | -------------------------------------------------------------------------------- /src/screens/task-details/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import { Color } from "../../assets/color"; 3 | import { Font } from "../../assets/fonts/font"; 4 | 5 | export default StyleSheet.create({ 6 | container: { 7 | flex: 1, 8 | backgroundColor: Color.ENSIGN 9 | }, 10 | contentContainer: { 11 | paddingHorizontal: 20, 12 | paddingTop: 20 13 | }, 14 | baseInfoContainer: { 15 | flexDirection: 'row', 16 | justifyContent: 'space-between' 17 | }, 18 | nameLabel: { 19 | fontSize: 35 20 | }, 21 | dateCreatedInfoContainer: { 22 | }, 23 | dateCreatedLabel: { 24 | fontSize: 20 25 | }, 26 | statusInfoContainer: { 27 | flexDirection: 'row', 28 | justifyContent: 'space-between', 29 | paddingVertical: 25 30 | }, 31 | statusLabel: { 32 | fontSize: 25, 33 | color: Color.OCEAN 34 | }, 35 | deadlineLabel: { 36 | fontSize: 20, 37 | color: Color.SUN 38 | }, 39 | userChipsInfoContainer: { 40 | flexDirection: 'row', 41 | justifyContent: 'space-between', 42 | paddingVertical: 10 43 | }, 44 | chipContainer: { 45 | alignItems: 'center', 46 | paddingHorizontal: 3 47 | }, 48 | userChip: { 49 | 50 | }, 51 | parentChipContainer: { 52 | flexDirection: 'row', 53 | justifyContent: 'flex-start', 54 | paddingVertical: 10 55 | }, 56 | parentChip: { 57 | 58 | }, 59 | childrenChipContainer: { 60 | flexDirection: 'row', 61 | justifyContent: 'flex-start', 62 | paddingVertical: 10 63 | }, 64 | childChip: { 65 | }, 66 | descriptionContainer: { 67 | paddingVertical: 10 68 | }, 69 | description: { 70 | fontSize: 18, 71 | paddingVertical: 5 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eugene-react-native-task-management-system", 3 | "version": "0.0.1", 4 | "description": "Eugene's React Native based app Task Management System.", 5 | "private": true, 6 | "rnpm": { 7 | "assets": [ 8 | "./src/assets/fonts" 9 | ] 10 | }, 11 | "scripts": { 12 | "start": "node node_modules/react-native/local-cli/cli.js start", 13 | "emulator": "~/Android/Sdk/tools/emulator -avd Nexus_5X_API_27", 14 | "test": "jest" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/EugeneOwl/react-native-task-management-system" 19 | }, 20 | "author": "e.ivanov", 21 | "dependencies": { 22 | "@react-native-community/async-storage": "^1.2.1", 23 | "color": "^3.1.0", 24 | "react": "16.8.3", 25 | "react-native": "0.59.1", 26 | "react-native-datepicker": "^1.7.2", 27 | "react-native-elements": "^1.1.0", 28 | "react-native-gesture-handler": "^1.1.0", 29 | "react-native-material-dialog": "^0.7.6", 30 | "react-native-paper": "^2.13.0", 31 | "react-native-vector-icons": "^6.4.1", 32 | "react-navigation": "^3.4.0", 33 | "react-navigation-material-bottom-tabs": "^1.0.0" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.3.4", 37 | "@babel/runtime": "^7.3.4", 38 | "@types/jest": "^24.0.11", 39 | "@types/react": "^16.8.8", 40 | "@types/react-native": "^0.57.40", 41 | "@types/react-test-renderer": "^16.8.1", 42 | "babel-jest": "^24.5.0", 43 | "jest": "^24.5.0", 44 | "metro-react-native-babel-preset": "^0.53.1", 45 | "react-test-renderer": "16.8.3", 46 | "typescript": "^3.3.3333" 47 | }, 48 | "jest": { 49 | "preset": "react-native" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/lib/components/headers/screen-header/ScreenHeader.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Text, TouchableOpacity, View } from "react-native"; 4 | import { Icon } from "react-native-elements"; 5 | import { Color } from "../../../../assets/color"; 6 | 7 | export interface ScreenHeaderIcon { 8 | name: string; 9 | color?: string; 10 | onPress: () => void; 11 | } 12 | 13 | interface Props { 14 | text: string; 15 | leftIcon?: ScreenHeaderIcon | null; 16 | rightIcon?: ScreenHeaderIcon | null; 17 | } 18 | 19 | const ScreenHeader = (props: Props) => { 20 | const { text, leftIcon, rightIcon } = props; 21 | 22 | return ( 23 | 24 | null } 28 | > 29 | { leftIcon && 30 | 35 | } 36 | 37 | 38 | 39 | 40 | { text } 41 | 42 | 43 | 44 | null } 48 | > 49 | { rightIcon && 50 | 55 | } 56 | 57 | 58 | ) 59 | }; 60 | 61 | export { ScreenHeader } 62 | -------------------------------------------------------------------------------- /src/lib/components/form-controls/form-picker/FormPickerControl.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from 'react'; 3 | import { Component } from 'react'; 4 | import { ActivityIndicator, Snackbar, Text } from 'react-native-paper'; 5 | import { FormPickerItem } from "./utils/form-picker-item"; 6 | import { Picker, View } from "react-native"; 7 | import { Color } from "../../../../assets/color"; 8 | 9 | interface Props { 10 | items: FormPickerItem[]; 11 | onValueChange: (itemValue: number) => void; 12 | defaultItem?: FormPickerItem; 13 | label: string; 14 | } 15 | 16 | interface State { 17 | selectedItemValue: number; 18 | } 19 | 20 | export class FormPickerControl extends Component { 21 | 22 | state: State = { 23 | selectedItemValue: 0 24 | }; 25 | 26 | private defaultItem: FormPickerItem = { label: '', value: 0 }; 27 | 28 | private onValueChange(newValue: number): void { 29 | this.setState({ selectedItemValue: newValue }); 30 | this.props.onValueChange(newValue); 31 | } 32 | 33 | render() { 34 | const { items, defaultItem, label } = this.props; 35 | items.unshift(defaultItem || this.defaultItem); 36 | 37 | return ( 38 | 39 | { label } 40 | this.onValueChange(itemValue) } 44 | > 45 | { 46 | items.map(item => ( 47 | 52 | )) 53 | } 54 | 55 | 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ios/LeadAnthill-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | NSLocationWhenInUseUsageDescription 40 | 41 | NSAppTransportSecurity 42 | 43 | 44 | NSExceptionDomains 45 | 46 | localhost 47 | 48 | NSExceptionAllowsInsecureHTTPLoads 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/lib/components/form-controls/form-date-picker/FormDatePickerControl.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from 'react'; 3 | import { Component } from 'react'; 4 | import { Text } from 'react-native-paper'; 5 | import { View } from "react-native"; 6 | // @ts-ignore 7 | import DatePicker from 'react-native-datepicker'; 8 | import { Color } from "../../../../assets/color"; 9 | 10 | interface Props { 11 | label: string; 12 | onValueChange: (date: string) => void; 13 | } 14 | 15 | interface State { 16 | date: string; 17 | } 18 | 19 | export class FormDatePickerControl extends Component { 20 | 21 | state: State = { 22 | date: '' 23 | }; 24 | 25 | private onValueChange(newValue: string): void { 26 | this.setState({ date: newValue }); 27 | this.props.onValueChange(newValue); 28 | } 29 | 30 | render() { 31 | const { label } = this.props; 32 | 33 | return ( 34 | 35 | { label } 36 | this.onValueChange(date) } 67 | /> 68 | 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/lib/network/api/endpoints-constant.ts: -------------------------------------------------------------------------------- 1 | import { EnvironmentType, serverUrl } from "./environment-properties"; 2 | 3 | const env = EnvironmentType.REMOTE; 4 | 5 | class Auth { 6 | static readonly base = serverUrl(env) + '/auth'; 7 | static readonly login = Auth.base + '/login'; 8 | } 9 | 10 | class Teams { 11 | static readonly base = serverUrl(env) + '/teams'; 12 | static readonly baseByProjectId = (projectId: number) => Teams.base + '?projectId=' + projectId; 13 | 14 | static readonly create = Teams.base + '/new'; 15 | static readonly createByProjectId = (projectId: number) => Teams.create + '?projectId=' + projectId; 16 | } 17 | 18 | class Users { 19 | static readonly base = serverUrl(env) + '/users'; 20 | static readonly baseByProjectId = (projectId: number) => Users.base + '?projectId=' + projectId; 21 | 22 | static readonly create = Users.base + '/new'; 23 | static readonly createByProjectId = (projectId: number) => Users.create + '?projectId=' + projectId; 24 | 25 | static readonly details = (id: number) => Users.base + '/details/' + id; 26 | } 27 | 28 | class Tasks { 29 | static readonly base = serverUrl(env) + '/tasks'; 30 | static readonly baseByProjectId = (projectId: number, search: string, status: number, expiredOnly: boolean) => 31 | `${ Tasks.base }?projectId=${ projectId }&pattern=${ search }&taskStatusId=${ status ? status : '' }&expiredOnly=${expiredOnly ? 'true' : ''}`; 32 | 33 | static readonly create = Tasks.base + '/new'; 34 | static readonly createByProjectId = (projectId: number) => Tasks.create + '?projectId=' + projectId; 35 | 36 | static readonly details = (id: number) => Tasks.base + '/details/' + id; 37 | 38 | static readonly timeline = (search: string) => Tasks.base + '/timeline?pattern=' + search; 39 | static readonly timelog = (id: number) => Tasks.base + '/timelog/' + id; 40 | static readonly status = (taskId: number, statusId: number, userId: number) => 41 | Tasks.base + '/status/' + taskId + '?taskStatusId=' + statusId + '&userId=' + userId; 42 | } 43 | 44 | export class Endpoint { 45 | 46 | static readonly AUTH = Auth; 47 | static readonly TEAMS = Teams; 48 | static readonly USERS = Users; 49 | static readonly TASKS = Tasks; 50 | } 51 | -------------------------------------------------------------------------------- /ios/LeadAnthillTests/LeadAnthillTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | #import 12 | #import 13 | 14 | #define TIMEOUT_SECONDS 600 15 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 16 | 17 | @interface LeadAnthillTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation LeadAnthillTests 22 | 23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 24 | { 25 | if (test(view)) { 26 | return YES; 27 | } 28 | for (UIView *subview in [view subviews]) { 29 | if ([self findSubviewInView:subview matching:test]) { 30 | return YES; 31 | } 32 | } 33 | return NO; 34 | } 35 | 36 | - (void)testRendersWelcomeScreen 37 | { 38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 40 | BOOL foundElement = NO; 41 | 42 | __block NSString *redboxError = nil; 43 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 44 | if (level >= RCTLogLevelError) { 45 | redboxError = message; 46 | } 47 | }); 48 | 49 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 50 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 51 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 52 | 53 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 54 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 55 | return YES; 56 | } 57 | return NO; 58 | }]; 59 | } 60 | 61 | RCTSetLogFunction(RCTDefaultLogFunction); 62 | 63 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 64 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 65 | } 66 | 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Authorize 2 | drawing 3 | 4 | ## View tasks 5 | drawing 6 | 7 | ## Add new ones 8 | drawing 9 | 10 | ## View task details 11 | drawing 12 | 13 | ## Log time 14 | drawing 15 | 16 | ## Change status 17 | drawing 18 | 19 | ## Look up throw status flow 20 | drawing 21 | 22 | ## View users 23 | drawing 24 | 25 | ## Add new ones 26 | drawing 27 | 28 | ## Navigate throw screens 29 | drawing 30 | 31 | ## View teams 32 | drawing 33 | 34 | ## Add new ones 35 | drawing 36 | 37 | ## View profile 38 | drawing 39 | -------------------------------------------------------------------------------- /src/navigation/AppBottomTabNavigator.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { createAppContainer, createBottomTabNavigator } from 'react-navigation'; 3 | // @ts-ignore 4 | import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs'; 5 | import * as React from "react"; 6 | import { AppTab } from "./routes"; 7 | import { NavigationIcon } from "../lib/components/icons/navigation-icon/NavigationIcon"; 8 | import { Color } from "../assets/color"; 9 | import { TaskStatusFlowScreen } from "../screens/task-status-flow/TaskStatusFlowScreen"; 10 | import TaskStackNavigator from './TasksStackNavigator'; 11 | import UsersDrawerNavigator from './UsersDrawerNavigator'; 12 | import { UserDetailsScreen } from "../screens/user-details/UserDetailsScreen"; 13 | 14 | const routeConfigs = { 15 | [AppTab.TASKS]: { 16 | screen: TaskStackNavigator, 17 | navigationOptions: { 18 | title: 'Tasks', 19 | // @ts-ignore 20 | tabBarIcon: ({ focused, tintColor }) => ( 21 | 22 | ) 23 | } 24 | }, 25 | [AppTab.TASK_STATUS_FLOW]: { 26 | screen: TaskStatusFlowScreen, 27 | navigationOptions: { 28 | title: 'Status Flow', 29 | // @ts-ignore 30 | tabBarIcon: ({ focused, tintColor }) => ( 31 | 32 | ) 33 | } 34 | }, 35 | [AppTab.USERS]: { 36 | screen: UsersDrawerNavigator, 37 | navigationOptions: { 38 | title: 'Users', 39 | // @ts-ignore 40 | tabBarIcon: ({ focused, tintColor }) => ( 41 | 42 | ) 43 | } 44 | }, 45 | [AppTab.PROFILE]: { 46 | screen: UserDetailsScreen, 47 | navigationOptions: { 48 | title: 'Profile', 49 | // @ts-ignore 50 | tabBarIcon: ({ focused, tintColor }) => ( 51 | 52 | ) 53 | } 54 | } 55 | }; 56 | 57 | const navigatorConfig = { 58 | shifting: true, 59 | labeled: true, 60 | activeColor: Color.OCEAN, 61 | inactiveColor: Color.LIGHT, 62 | barStyle: { backgroundColor: Color.CARBON }, 63 | initialRouteName: AppTab.TASKS, 64 | backBehavior: 'none' 65 | }; 66 | 67 | const materialBottomTabNavigator = createMaterialBottomTabNavigator(routeConfigs, navigatorConfig); 68 | 69 | export default createAppContainer(materialBottomTabNavigator) 70 | -------------------------------------------------------------------------------- /ios/LeadAnthill/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | LeadAnthill 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSLocationWhenInUseUsageDescription 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UIViewControllerBasedStatusBarAppearance 42 | 43 | NSAppTransportSecurity 44 | 45 | NSAllowsArbitraryLoads 46 | 47 | NSExceptionDomains 48 | 49 | localhost 50 | 51 | NSExceptionAllowsInsecureHTTPLoads 52 | 53 | 54 | 55 | 56 | UIAppFonts 57 | 58 | AntDesign.ttf 59 | Entypo.ttf 60 | EvilIcons.ttf 61 | Feather.ttf 62 | FontAwesome.ttf 63 | FontAwesome5_Brands.ttf 64 | FontAwesome5_Regular.ttf 65 | FontAwesome5_Solid.ttf 66 | Foundation.ttf 67 | Ionicons.ttf 68 | MaterialCommunityIcons.ttf 69 | MaterialIcons.ttf 70 | Octicons.ttf 71 | SimpleLineIcons.ttf 72 | Zocial.ttf 73 | Sniglet-ExtraBold.ttf 74 | Sniglet-Regular.ttf 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ios/LeadAnthill/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/screens/login/LoginScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { Image, ScrollView, View } from "react-native"; 5 | import { AppAuthSwitch } from "../../navigation/routes"; 6 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 7 | import { Button, TextInput } from 'react-native-paper'; 8 | import { Color } from "../../assets/color"; 9 | import { authService } from "../../lib/network/http-services/auth-service"; 10 | import { Login, LoginResponse } from "../../lib/models/auth/login"; 11 | import AsyncStorage from '@react-native-community/async-storage'; 12 | import { AsyncStorageKey } from "../../consts/AsyncStorageKey"; 13 | import { HttpError } from "../../lib/network/common/http-error"; 14 | import { SnackNotification } from "../../lib/components/snack-notification/SnackNotification"; 15 | 16 | interface State { 17 | usernameControlValue: string; 18 | passwordControlValue: string; 19 | snackBarMessage: string; 20 | httpReqInProcess: boolean; 21 | } 22 | 23 | export class LoginScreen extends Component { 24 | 25 | state: State = { 26 | usernameControlValue: '', 27 | passwordControlValue: '', 28 | snackBarMessage: '', 29 | httpReqInProcess: false 30 | }; 31 | 32 | // @ts-ignore 33 | private navigation = this.props.navigation; 34 | private iconLogoSource = require('../../assets/images/react_dark_icon/icons8-react-native-filled-200.png'); 35 | 36 | private navigateToApp(): void { 37 | this.navigation.navigate(AppAuthSwitch.APP); 38 | } 39 | 40 | private get anyRequiredFormFieldAbsent(): boolean { 41 | return !this.state.usernameControlValue || !this.state.passwordControlValue; 42 | } 43 | 44 | private get formData(): Login { 45 | return { 46 | username: this.state.usernameControlValue, 47 | password: this.state.passwordControlValue 48 | }; 49 | } 50 | 51 | private sendFormData(): void { 52 | this.setState({ httpReqInProcess: true }); 53 | 54 | authService.login(this.formData) 55 | .then((response: LoginResponse) => this.processResponse(response)) 56 | .catch((error: HttpError) => this.processError(error)); 57 | } 58 | 59 | private processResponse(response: LoginResponse): void { 60 | this.setState({ httpReqInProcess: false }); 61 | 62 | if (response.error) { 63 | this.showOnStackBar(response.error.message); 64 | return; 65 | } 66 | AsyncStorage.setItem(AsyncStorageKey.JWT_TOKEN, response.token); 67 | this.navigateToApp(); 68 | } 69 | 70 | private processError(error: HttpError): void { 71 | this.setState({ httpReqInProcess: false }); 72 | this.showOnStackBar(error.message); 73 | } 74 | 75 | private showOnStackBar(message: string): void { 76 | this.setState({ snackBarMessage: message }) 77 | } 78 | 79 | render(): ReactNode { 80 | 81 | return ( 82 | 83 | 84 | 85 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | this.setState({ usernameControlValue: text }) } 103 | /> 104 | 105 | this.setState({ passwordControlValue: text }) } 112 | /> 113 | 114 | 115 | 126 | 127 | 128 | 129 | 130 | this.showOnStackBar('') } 133 | /> 134 | 135 | ); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/screens/user-list/UserListScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { RefreshControl, ScrollView, View } from "react-native"; 5 | import { UsersStack } from "../../navigation/routes"; 6 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 7 | import { FAB, List } from 'react-native-paper'; 8 | import { HttpError } from "../../lib/network/common/http-error"; 9 | import { CentralSpinner } from "../../lib/components/central-spinner/CentralSpinner"; 10 | import { SnackNotification } from "../../lib/components/snack-notification/SnackNotification"; 11 | import { userService } from "../../lib/network/http-services/user-service"; 12 | import { UserListItem } from "../../lib/models/user/user"; 13 | import { InitialsBasedAvatar } from "../../lib/components/icons/initials-based-avatar/InitialsBasedAvatar"; 14 | import { UserListChips } from "./user-list-chips/UserListChips"; 15 | 16 | interface State { 17 | users: UserListItem[]; 18 | snackBarMessage: string; 19 | refreshing: boolean; 20 | httpReqInProcess: boolean; 21 | } 22 | 23 | export class UserListScreen extends Component { 24 | 25 | state: State = { 26 | users: [], 27 | snackBarMessage: '', 28 | refreshing: false, 29 | httpReqInProcess: false 30 | }; 31 | 32 | // @ts-ignore 33 | private navigation = this.props.navigation; 34 | 35 | componentDidMount(): void { 36 | this.requestContent(); 37 | } 38 | 39 | private requestContent(byRefresh?: boolean): void { 40 | this.setState(byRefresh ? { refreshing: true } : { httpReqInProcess: true }); 41 | 42 | userService.getAll(10) 43 | .then((response: UserListItem[]) => this.processResponse(response)) 44 | .catch((error: HttpError) => this.processError(error)) 45 | .finally(() => this.setState({ httpReqInProcess: false, refreshing: false })); 46 | } 47 | 48 | private processResponse(response: UserListItem[]): void { 49 | // @ts-ignore 50 | if (response.error) { 51 | // @ts-ignore 52 | this.showOnStackBar(response.error.message); 53 | return; 54 | } 55 | this.setState({ users: response }); 56 | } 57 | 58 | private processError(error: HttpError): void { 59 | this.showOnStackBar(error.message); 60 | } 61 | 62 | private showOnStackBar(message: string): void { 63 | this.setState({ snackBarMessage: message }) 64 | } 65 | 66 | private extractUsernameLabel(user: UserListItem): string { 67 | const maxLength = (!user.tasksInProgress || !user.tasksToPerform) ? 50 : 16; 68 | if (user.username.length < maxLength) { 69 | return user.username; 70 | } 71 | return user.username.substring(0, maxLength - 2).concat('...'); 72 | } 73 | 74 | private navigateToUserDetails = (id: number) => this.navigation.navigate(UsersStack.USER_DETAILS, { id: id }); 75 | private navigateToCreateUser = () => this.navigation.navigate(UsersStack.CREATE_USER); 76 | 77 | render(): ReactNode { 78 | const sideNavIcon = { name: 'first-page', onPress: () => this.navigation.openDrawer() }; 79 | 80 | return ( 81 | 82 | 83 | 84 | 85 | 86 | this.requestContent(true) } 93 | /> 94 | } 95 | > 96 | 97 | 98 | { this.state.users.map(user => ( 99 | } 103 | right={ () => 104 | } 105 | onPress={ () => this.navigateToUserDetails(user.id) } 106 | key={ user.id } 107 | /> 108 | ) 109 | ) 110 | } 111 | 112 | 113 | 114 | 115 | this.navigateToCreateUser() } 120 | /> 121 | 122 | this.showOnStackBar('') } 125 | /> 126 | 127 | ) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/screens/user-details/UserDetailsScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { View } from "react-native"; 5 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 6 | import { Chip, Text } from "react-native-paper"; 7 | import AsyncStorage from "@react-native-community/async-storage"; 8 | import { AsyncStorageKey } from "../../consts/AsyncStorageKey"; 9 | import { UserDetails } from "../../lib/models/user/user"; 10 | import { userService } from "../../lib/network/http-services/user-service"; 11 | import { HttpError } from "../../lib/network/common/http-error"; 12 | import { CentralSpinner } from "../../lib/components/central-spinner/CentralSpinner"; 13 | import { SnackNotification } from "../../lib/components/snack-notification/SnackNotification"; 14 | import { InitialsBasedAvatar } from "../../lib/components/icons/initials-based-avatar/InitialsBasedAvatar"; 15 | import { appPaperTheme } from "../../assets/paper-theme"; 16 | import { Color } from "../../assets/color"; 17 | import { AppAuthSwitch } from "../../navigation/routes"; 18 | 19 | const defaultUserDetails: UserDetails = { 20 | id: 0, 21 | username: '', 22 | teams: [], 23 | role: '', 24 | timeLogged: '', 25 | error: { message: '', status: 200 } 26 | }; 27 | 28 | interface State { 29 | id: number, 30 | userDetails: UserDetails; 31 | snackBarMessage: string; 32 | httpReqInProcess: boolean; 33 | } 34 | 35 | export class UserDetailsScreen extends Component { 36 | 37 | state: State = { 38 | id: 0, 39 | userDetails: defaultUserDetails, 40 | snackBarMessage: '', 41 | httpReqInProcess: false 42 | }; 43 | 44 | // @ts-ignore 45 | private navigation = this.props.navigation; 46 | 47 | componentDidMount(): void { 48 | this.state.id = this.navigation.state.params ? this.navigation.state.params.id : 0; 49 | this.requestContent(); 50 | } 51 | 52 | private requestContent(): void { 53 | this.setState({ httpReqInProcess: true }); 54 | 55 | userService.getDetails(this.state.id || 10) 56 | .then((response: UserDetails) => this.processResponse(response)) 57 | .catch((error: HttpError) => this.processError(error)) 58 | .finally(() => this.setState({ httpReqInProcess: false })); 59 | } 60 | 61 | private processResponse(response: UserDetails): void { 62 | if (response.error) { 63 | this.showOnStackBar(response.error.message); 64 | return; 65 | } 66 | this.setState({ userDetails: response }); 67 | } 68 | 69 | private processError(error: HttpError): void { 70 | this.showOnStackBar(error.message); 71 | } 72 | 73 | private showOnStackBar(message: string): void { 74 | this.setState({ snackBarMessage: message }) 75 | } 76 | 77 | private onLogoutPress() { 78 | AsyncStorage.removeItem(AsyncStorageKey.JWT_TOKEN); 79 | this.navigation.navigate(AppAuthSwitch.AUTH_LOADING); 80 | } 81 | 82 | render(): ReactNode { 83 | const goBackIcon = { name: 'keyboard-arrow-left', onPress: () => this.navigation.goBack() }; 84 | const logOutIcon = { name: 'exit-to-app', onPress: () => this.onLogoutPress() }; 85 | const chipTheme = JSON.parse(JSON.stringify(appPaperTheme)); 86 | chipTheme.colors.text = Color.FRANT; 87 | 88 | return ( 89 | 90 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | { this.state.userDetails.username } 107 | { this.state.userDetails.role } 108 | { this.state.userDetails.timeLogged } 109 | 110 | 111 | { this.state.userDetails.teams.map(team => 112 | 113 | { team } 114 | 115 | ) 116 | } 117 | 118 | 119 | 120 | 121 | 122 | 123 | this.showOnStackBar('') } 126 | /> 127 | 128 | ) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/screens/team-list/TeamListScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { RefreshControl, ScrollView, View } from "react-native"; 5 | import { TeamsStack, UsersStack } from "../../navigation/routes"; 6 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 7 | import { FAB, List, Text } from 'react-native-paper'; 8 | import { Color } from "../../assets/color"; 9 | import { TeamListItem } from "../../lib/models/team/team-list"; 10 | import { teamService } from "../../lib/network/http-services/team-service"; 11 | import { HttpError } from "../../lib/network/common/http-error"; 12 | import { CentralSpinner } from "../../lib/components/central-spinner/CentralSpinner"; 13 | import { SnackNotification } from "../../lib/components/snack-notification/SnackNotification"; 14 | 15 | interface State { 16 | teams: TeamListItem[]; 17 | snackBarMessage: string; 18 | refreshing: boolean; 19 | httpReqInProcess: boolean; 20 | } 21 | 22 | export class TeamListScreen extends Component { 23 | 24 | state: State = { 25 | teams: [], 26 | snackBarMessage: '', 27 | refreshing: false, 28 | httpReqInProcess: false 29 | }; 30 | 31 | // @ts-ignore 32 | private navigation = this.props.navigation; 33 | 34 | componentDidMount(): void { 35 | this.requestContent(); 36 | } 37 | 38 | private requestContent(byRefresh?: boolean): void { 39 | this.setState(byRefresh ? { refreshing: true } : { httpReqInProcess: true }); 40 | 41 | teamService.getAll(10) 42 | .then((response: TeamListItem[]) => this.processResponse(response)) 43 | .catch((error: HttpError) => this.processError(error)) 44 | .finally(() => this.setState({ httpReqInProcess: false, refreshing: false })); 45 | } 46 | 47 | private processResponse(response: TeamListItem[]): void { 48 | // @ts-ignore 49 | if (response.error) { 50 | // @ts-ignore 51 | this.showOnStackBar(response.error.message); 52 | return; 53 | } 54 | this.setState({ teams: response }); 55 | } 56 | 57 | private processError(error: HttpError): void { 58 | this.showOnStackBar(error.message); 59 | } 60 | 61 | private showOnStackBar(message: string): void { 62 | this.setState({ snackBarMessage: message }) 63 | } 64 | 65 | private navigateToUserDetails = (id: number) => this.navigation.navigate(UsersStack.USER_DETAILS, { id: id }); 66 | private navigateToCreateTeam = () => this.navigation.navigate(TeamsStack.CREATE_TEAM); 67 | 68 | 69 | render(): ReactNode { 70 | const sideNavIcon = { name: 'first-page', onPress: () => this.navigation.openDrawer() }; 71 | 72 | return ( 73 | 74 | 75 | 76 | 77 | 78 | this.requestContent(true) } 85 | /> 86 | } 87 | > 88 | 89 | 90 | { this.state.teams.map(team => ( 91 | } 94 | description={ team.timeLogged } 95 | key={ team.id } 96 | > 97 | { team.members.map(member => 98 | 102 | 103 | } 104 | right={ () => ( 105 | 106 | { member.role } 107 | 108 | ) } 109 | description={ member.timeLogged } 110 | onPress={ () => this.navigateToUserDetails(member.id) } 111 | key={ member.id } 112 | /> 113 | ) 114 | } 115 | 116 | ) 117 | ) 118 | } 119 | 120 | 121 | 122 | 123 | this.navigateToCreateTeam() } 128 | /> 129 | 130 | this.showOnStackBar('') } 133 | /> 134 | 135 | ) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /ios/LeadAnthill.xcodeproj/xcshareddata/xcschemes/LeadAnthill.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /ios/LeadAnthill.xcodeproj/xcshareddata/xcschemes/LeadAnthill-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /src/screens/task-status-flow/TaskStatusFlowScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { RefreshControl, ScrollView, Text, View } from "react-native"; 5 | import { taskService } from "../../lib/network/http-services/task-service"; 6 | import { HttpError } from "../../lib/network/common/http-error"; 7 | import { TasksStack } from "../../navigation/routes"; 8 | import { SearchHeader } from "../../lib/components/headers/search-header/SearchHeader"; 9 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 10 | import { CentralSpinner } from "../../lib/components/central-spinner/CentralSpinner"; 11 | import { List } from "react-native-paper"; 12 | import { Color } from "../../assets/color"; 13 | import { SnackNotification } from "../../lib/components/snack-notification/SnackNotification"; 14 | import { TaskStatus } from "../../lib/models/task/task-status"; 15 | import { TaskTimeline } from "../../lib/models/task/task-timeline"; 16 | 17 | interface State { 18 | taskTimeliness: TaskTimeline[]; 19 | searchOpened: boolean; 20 | searchText: string; 21 | snackBarMessage: string; 22 | refreshing: boolean; 23 | httpReqInProcess: boolean; 24 | } 25 | 26 | export class TaskStatusFlowScreen extends Component { 27 | 28 | state: State = { 29 | taskTimeliness: [], 30 | searchOpened: false, 31 | searchText: '', 32 | snackBarMessage: '', 33 | refreshing: false, 34 | httpReqInProcess: false 35 | }; 36 | 37 | // @ts-ignore 38 | private navigation = this.props.navigation; 39 | 40 | componentDidMount(): void { 41 | this.requestContent(); 42 | } 43 | 44 | private requestContent(byRefresh?: boolean): void { 45 | this.setState(byRefresh ? { refreshing: true } : { httpReqInProcess: true }); 46 | 47 | taskService.getTimeline(this.state.searchText) 48 | .then((response: TaskTimeline[]) => this.processResponse(response)) 49 | .catch((error: HttpError) => this.processError(error)) 50 | .finally(() => this.setState({ httpReqInProcess: false, refreshing: false })); 51 | } 52 | 53 | private processResponse(response: TaskTimeline[]): void { 54 | // @ts-ignore 55 | if (response.error) { 56 | // @ts-ignore 57 | this.showOnStackBar(response.error.message); 58 | return; 59 | } 60 | this.setState({ taskTimeliness: response }); 61 | } 62 | 63 | private processError = (error: HttpError) => this.showOnStackBar(error.message); 64 | 65 | private showOnStackBar = (message: string) => this.setState({ snackBarMessage: message }); 66 | 67 | private getIcon(status: string): string { 68 | try { 69 | const keys = Object.keys(TaskStatus.value).map(key => key.replace(/_/g, ' ')); 70 | const key = (keys.find(key => key === status) as string).replace(/ /g, '_'); 71 | // @ts-ignore 72 | return TaskStatus.value[key].icon; 73 | } catch (e) { 74 | return 'tab'; 75 | } 76 | } 77 | 78 | private navigateToUserDetails = (id: number) => this.navigation.navigate(TasksStack.TASK_DETAILS, { id: id }); 79 | 80 | render(): ReactNode { 81 | const openSearchIcon = { name: 'search', onPress: () => this.setState({ searchOpened: true }) }; 82 | const searchIcon = { 83 | onPress: (text: string) => { 84 | this.state.searchText = text; 85 | this.requestContent(); 86 | } 87 | }; 88 | const closeSearchIcon = { 89 | onPress: () => { 90 | this.setState({ searchOpened: false }); 91 | this.state.searchText = ''; 92 | this.requestContent(); 93 | } 94 | }; 95 | 96 | return ( 97 | 98 | { this.state.searchOpened ? 99 | 100 | : 101 | 102 | } 103 | 104 | 105 | 106 | this.requestContent(true) } 113 | /> 114 | } 115 | > 116 | 117 | 118 | } 121 | description="By current filter options" 122 | expanded={ true } 123 | > 124 | { this.state.taskTimeliness.map((timeline: TaskTimeline) => 125 | } 130 | right={ () => ( 131 | 132 | { timeline.status } 133 | { `${ timeline.datecreated } at ${ timeline.timecreated }` } 135 | 136 | ) } 137 | description={ 'By ' + timeline.username } 138 | key={ `${ timeline.taskid }${ timeline.status }` } 139 | /> 140 | ) 141 | } 142 | 143 | 144 | 145 | 146 | 147 | this.showOnStackBar('') } 150 | /> 151 | 152 | ); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/screens/create-user/CreateUserScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { ScrollView, View } from "react-native"; 5 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 6 | import { HttpError } from "../../lib/network/common/http-error"; 7 | import { SnackNotification } from "../../lib/components/snack-notification/SnackNotification"; 8 | import { Button, HelperText, Text, TextInput } from "react-native-paper"; 9 | import { Color } from "../../assets/color"; 10 | import { UserCreateRequest, UserListItem, UserPreCreateData } from "../../lib/models/user/user"; 11 | import { InitialsBasedAvatar } from "../../lib/components/icons/initials-based-avatar/InitialsBasedAvatar"; 12 | import { Lookup } from "../../lib/models/common/Lookup"; 13 | import { userService } from "../../lib/network/http-services/user-service"; 14 | import { FormPickerControl } from "../../lib/components/form-controls/form-picker/FormPickerControl"; 15 | import { FormPickerItem } from "../../lib/components/form-controls/form-picker/utils/form-picker-item"; 16 | 17 | interface State { 18 | teams: Lookup[]; 19 | roles: Lookup[]; 20 | 21 | usernameControlValue: string; 22 | passwordControlValue: string; 23 | teamControlValue: number; 24 | roleControlValue: number; 25 | 26 | snackBarMessage: string; 27 | httpReqInProcess: boolean; 28 | } 29 | 30 | export class CreateUserScreen extends Component { 31 | 32 | state: State = { 33 | teams: [], 34 | roles: [], 35 | 36 | usernameControlValue: '', 37 | passwordControlValue: '', 38 | teamControlValue: 0, 39 | roleControlValue: 0, 40 | 41 | snackBarMessage: '', 42 | httpReqInProcess: false 43 | }; 44 | 45 | // @ts-ignore 46 | private navigation = this.props.navigation; 47 | 48 | componentDidMount(): void { 49 | this.requestPreCreateData(); 50 | } 51 | 52 | private requestPreCreateData(): void { 53 | this.setState({ httpReqInProcess: true }); 54 | 55 | userService.getPreCreateData(10) 56 | .then((response: UserPreCreateData) => this.processPreCreateResponse(response)) 57 | .catch((error: HttpError) => this.processError(error)) 58 | .finally(() => this.setState({ httpReqInProcess: false })); 59 | } 60 | 61 | private processPreCreateResponse(response: UserPreCreateData): void { 62 | if (response.error) { 63 | this.showOnStackBar(response.error.message); 64 | return; 65 | } 66 | this.setState({ teams: response.teams, roles: response.roles }); 67 | } 68 | 69 | private get anyRequiredFormFieldAbsent(): boolean { 70 | return !this.state.usernameControlValue 71 | || this.state.passwordControlValue.length < 6 72 | || !this.state.roleControlValue; 73 | } 74 | 75 | private get formData(): UserCreateRequest { 76 | return { 77 | projectId: 10, 78 | username: this.state.usernameControlValue, 79 | password: this.state.passwordControlValue, 80 | teamId: this.state.teamControlValue, 81 | roleId: this.state.roleControlValue 82 | }; 83 | } 84 | 85 | private sendFormData(): void { 86 | this.setState({ httpReqInProcess: true }); 87 | 88 | userService.create(this.formData) 89 | .then((response: UserListItem) => this.processCreateResponse(response)) 90 | .catch((error: HttpError) => this.processError(error)) 91 | .finally(() => this.setState({ httpReqInProcess: false })); 92 | } 93 | 94 | private processCreateResponse(response: UserListItem): void { 95 | if (response.error) { 96 | this.showOnStackBar(response.error.message); 97 | return; 98 | } 99 | this.navigation.goBack(); 100 | } 101 | 102 | private processError(error: HttpError): void { 103 | this.showOnStackBar(error.message); 104 | } 105 | 106 | private showOnStackBar(message: string): void { 107 | this.setState({ snackBarMessage: message }); 108 | } 109 | 110 | private get teamPickerItems(): FormPickerItem[] { 111 | return this.state.teams.map(lookup => ({ label: lookup.name, value: lookup.id })); 112 | } 113 | 114 | private get rolePickerItems(): FormPickerItem[] { 115 | return this.state.roles.map(lookup => ({ label: lookup.name, value: lookup.id })); 116 | } 117 | 118 | private get tooEasyPassword(): boolean { 119 | return this.state.passwordControlValue.length > 0 && this.state.passwordControlValue.length < 6; 120 | } 121 | 122 | render(): ReactNode { 123 | const goBackIcon = { name: 'keyboard-arrow-left', onPress: () => this.navigation.goBack() }; 124 | 125 | return ( 126 | 127 | 128 | 129 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | this.setState({ usernameControlValue: text }) } 148 | /> 149 | 15 }> 150 | Long username might look not pretty later 151 | 152 | 153 | this.setState({ passwordControlValue: text }) } 159 | /> 160 | 161 | Password is too easy 162 | 163 | 164 | this.setState({ teamControlValue: itemValue }) } 167 | label="Team" 168 | /> 169 | 170 | this.setState({ roleControlValue: itemValue }) } 173 | label="Role" 174 | /> 175 | 176 | 177 | 188 | 189 | 190 | 191 | this.showOnStackBar('') } 194 | /> 195 | 196 | ); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/screens/create-task/CreateTaskScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { ScrollView, View } from "react-native"; 5 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 6 | import { HttpError } from "../../lib/network/common/http-error"; 7 | import { SnackNotification } from "../../lib/components/snack-notification/SnackNotification"; 8 | import { UserCreateRequest, UserListItem, UserPreCreateData } from "../../lib/models/user/user"; 9 | import { Lookup } from "../../lib/models/common/Lookup"; 10 | import { userService } from "../../lib/network/http-services/user-service"; 11 | import { FormPickerItem } from "../../lib/components/form-controls/form-picker/utils/form-picker-item"; 12 | import { taskService } from "../../lib/network/http-services/task-service"; 13 | import { TaskCreateRequest, TaskPreCreateData } from "../../lib/models/task/task"; 14 | import { InitialsBasedAvatar } from "../../lib/components/icons/initials-based-avatar/InitialsBasedAvatar"; 15 | import { Button, HelperText, Text, TextInput } from "react-native-paper"; 16 | import { FormPickerControl } from "../../lib/components/form-controls/form-picker/FormPickerControl"; 17 | import { Color } from "../../assets/color"; 18 | import { FormDatePickerControl } from "../../lib/components/form-controls/form-date-picker/FormDatePickerControl"; 19 | 20 | interface State { 21 | executors: Lookup[]; 22 | parents: Lookup[]; 23 | 24 | nameControlValue: string; 25 | descriptionControlValue: string; 26 | deadlineControlValue: string; 27 | parentControlValue: number; 28 | executorControlValue: number; 29 | 30 | snackBarMessage: string; 31 | httpReqInProcess: boolean; 32 | } 33 | 34 | export class CreateTaskScreen extends Component { 35 | 36 | state: State = { 37 | executors: [], 38 | parents: [], 39 | 40 | nameControlValue: '', 41 | descriptionControlValue: '', 42 | deadlineControlValue: '', 43 | parentControlValue: 0, 44 | executorControlValue: 0, 45 | 46 | snackBarMessage: '', 47 | httpReqInProcess: false 48 | }; 49 | 50 | // @ts-ignore 51 | private navigation = this.props.navigation; 52 | 53 | componentDidMount(): void { 54 | this.requestPreCreateData(); 55 | } 56 | 57 | private requestPreCreateData(): void { 58 | this.setState({ httpReqInProcess: true }); 59 | 60 | taskService.getPreCreateData(10) 61 | .then((response: TaskPreCreateData) => this.processPreCreateResponse(response)) 62 | .catch((error: HttpError) => this.processError(error)) 63 | .finally(() => this.setState({ httpReqInProcess: false })); 64 | } 65 | 66 | private processPreCreateResponse(response: TaskPreCreateData): void { 67 | if (response.error) { 68 | this.showOnStackBar(response.error.message); 69 | return; 70 | } 71 | this.setState({ executors: response.executors, parents: response.parents }); 72 | } 73 | 74 | private get anyRequiredFormFieldAbsent(): boolean { 75 | return !this.state.nameControlValue; 76 | } 77 | 78 | private get formData(): TaskCreateRequest { 79 | const request: TaskCreateRequest = { 80 | projectId: 10, 81 | name: this.state.nameControlValue, 82 | createdBy: 11 83 | }; 84 | if (this.state.deadlineControlValue) { 85 | request.deadline = this.state.deadlineControlValue; 86 | } 87 | if (this.state.descriptionControlValue) { 88 | request.description = this.state.descriptionControlValue; 89 | } 90 | if (this.state.parentControlValue) { 91 | request.parent = this.state.parentControlValue; 92 | } 93 | if (this.state.executorControlValue) { 94 | request.assignedTo = this.state.executorControlValue; 95 | } 96 | return request; 97 | } 98 | 99 | private sendFormData(): void { 100 | this.setState({ httpReqInProcess: true }); 101 | console.log('Send form: ', this.formData); 102 | 103 | taskService.create(this.formData) 104 | .then((response: UserListItem) => this.processCreateResponse(response)) 105 | .catch((error: HttpError) => this.processError(error)) 106 | .finally(() => this.setState({ httpReqInProcess: false })); 107 | } 108 | 109 | private processCreateResponse(response: UserListItem): void { 110 | if (response.error) { 111 | this.showOnStackBar(response.error.message); 112 | return; 113 | } 114 | this.navigation.goBack(); 115 | } 116 | 117 | private processError(error: HttpError): void { 118 | this.showOnStackBar(error.message); 119 | } 120 | 121 | private showOnStackBar(message: string): void { 122 | this.setState({ snackBarMessage: message }); 123 | } 124 | 125 | private get executorPickerItems(): FormPickerItem[] { 126 | return this.state.executors.map(lookup => ({ label: lookup.name, value: lookup.id })); 127 | } 128 | 129 | private get parentPickerItems(): FormPickerItem[] { 130 | return this.state.parents.map(lookup => ({ label: lookup.name, value: lookup.id })); 131 | } 132 | 133 | render(): ReactNode { 134 | const goBackIcon = { name: 'keyboard-arrow-left', onPress: () => this.navigation.goBack() }; 135 | 136 | return ( 137 | 138 | 139 | 140 | 146 | 147 | 148 | 149 | 150 | this.setState({ nameControlValue: text }) } 156 | /> 157 | 158 | this.setState({ descriptionControlValue: text }) } 164 | multiline={ true } 165 | /> 166 | 167 | this.setState({ executorControlValue: itemValue }) } 170 | label="Executor" 171 | /> 172 | 173 | this.setState({ parentControlValue: itemValue }) } 176 | label="Parent Task" 177 | /> 178 | 179 | this.setState({ deadlineControlValue: date }) } 182 | /> 183 | 184 | 185 | 186 | 197 | 198 | 199 | 200 | this.showOnStackBar('') } 203 | /> 204 | 205 | ); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/screens/task-list/TaskListScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { Alert, RefreshControl, ScrollView, View } from "react-native"; 5 | import { TasksStack } from "../../navigation/routes"; 6 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 7 | import { SearchHeader } from "../../lib/components/headers/search-header/SearchHeader"; 8 | import { TaskListItem } from "../../lib/models/task/task-list"; 9 | import { HttpError } from "../../lib/network/common/http-error"; 10 | import { taskService } from "../../lib/network/http-services/task-service"; 11 | import { CentralSpinner } from "../../lib/components/central-spinner/CentralSpinner"; 12 | import { FAB, List, Switch, Text } from "react-native-paper"; 13 | import { Color } from "../../assets/color"; 14 | import { SnackNotification } from "../../lib/components/snack-notification/SnackNotification"; 15 | import { FormPickerControl } from "../../lib/components/form-controls/form-picker/FormPickerControl"; 16 | import { TaskStatus } from "../../lib/models/task/task-status"; 17 | import { FormPickerItem } from "../../lib/components/form-controls/form-picker/utils/form-picker-item"; 18 | 19 | interface State { 20 | tasks: TaskListItem[]; 21 | searchOpened: boolean; 22 | searchText: string; 23 | statusControlValue: number; 24 | expiredOnlyControlValue: boolean; 25 | snackBarMessage: string; 26 | refreshing: boolean; 27 | httpReqInProcess: boolean; 28 | } 29 | 30 | export class TaskListScreen extends Component { 31 | 32 | state: State = { 33 | tasks: [], 34 | searchOpened: false, 35 | searchText: '', 36 | statusControlValue: 0, 37 | expiredOnlyControlValue: false, 38 | snackBarMessage: '', 39 | refreshing: false, 40 | httpReqInProcess: false 41 | }; 42 | 43 | // @ts-ignore 44 | private navigation = this.props.navigation; 45 | 46 | componentDidMount(): void { 47 | this.requestContent(); 48 | } 49 | 50 | private requestContent(byRefresh?: boolean): void { 51 | this.setState(byRefresh ? { refreshing: true } : { httpReqInProcess: true }); 52 | 53 | taskService.getAll( 54 | 10, 55 | this.state.searchText, 56 | this.state.statusControlValue, 57 | this.state.expiredOnlyControlValue 58 | ) 59 | .then((response: TaskListItem[]) => this.processResponse(response)) 60 | .catch((error: HttpError) => this.processError(error)) 61 | .finally(() => this.setState({ httpReqInProcess: false, refreshing: false })); 62 | } 63 | 64 | private processResponse(response: TaskListItem[]): void { 65 | // @ts-ignore 66 | if (response.error) { 67 | // @ts-ignore 68 | this.showOnStackBar(response.error.message); 69 | return; 70 | } 71 | this.setState({ tasks: response }); 72 | } 73 | 74 | private processError = (error: HttpError) => this.showOnStackBar(error.message); 75 | 76 | private showOnStackBar = (message: string) => this.setState({ snackBarMessage: message }); 77 | 78 | private navigateToTaskDetails = (id: number) => this.navigation.navigate(TasksStack.TASK_DETAILS, { id: id }); 79 | private navigateToCreateTask = () => this.navigation.navigate(TasksStack.CREATE_TASK); 80 | 81 | private get statusFilterValues(): FormPickerItem[] { 82 | const keys = Object.keys(TaskStatus.value); 83 | // @ts-ignore 84 | return keys.map(key => ({ label: TaskStatus.value[key].label, value: TaskStatus.value[key].id })); 85 | } 86 | 87 | private isExpired(dateString: string, status: string): boolean { 88 | if (status !== 'IN PROGRESS' && status !== 'TO PERFORM') { 89 | return false; 90 | } 91 | const pattern = /(\d{2})\.(\d{2})\.(\d{4})/; 92 | const date = new Date(dateString.replace(pattern,'$3-$2-$1')); 93 | return date < new Date(); 94 | } 95 | 96 | render(): ReactNode { 97 | const openSearchIcon = { name: 'search', onPress: () => this.setState({ searchOpened: true }) }; 98 | const searchIcon = { 99 | onPress: (text: string) => { 100 | this.state.searchText = text; 101 | this.requestContent(); 102 | } 103 | }; 104 | const closeSearchIcon = { onPress: () => this.setState({ searchOpened: false }) }; 105 | 106 | return ( 107 | 108 | { this.state.searchOpened ? 109 | 110 | : 111 | 112 | } 113 | 114 | 115 | 116 | { this.state.searchOpened ? 117 | 118 | 119 | { 122 | this.state.statusControlValue = itemValue; 123 | this.requestContent(); 124 | } } 125 | defaultItem={ { label: 'All', value: 0 } } 126 | label="Status" 127 | /> 128 | 129 | 130 | { `Overdue only (${ this.state.expiredOnlyControlValue ? 'on' : 'off' })` } 132 | { 135 | this.state.expiredOnlyControlValue = !this.state.expiredOnlyControlValue; 136 | this.requestContent(); 137 | } } 138 | /> 139 | 140 | 141 | : 142 | null 143 | } 144 | 145 | this.requestContent(true) } 152 | /> 153 | } 154 | > 155 | 156 | 157 | } 160 | description="By current filter options" 161 | expanded={ true } 162 | > 163 | { this.state.tasks.map(task => 164 | } 168 | right={ () => ( 169 | 170 | { task.status } 171 | 172 | { task.deadline } 173 | 174 | 175 | ) } 176 | description={ task.assignedTo.name ? 'Assigned to ' + task.assignedTo.name : '' } 177 | onPress={ () => this.navigateToTaskDetails(task.id) } 178 | key={ task.id } 179 | /> 180 | ) 181 | } 182 | 183 | 184 | 185 | 186 | 187 | this.navigateToCreateTask() } 192 | /> 193 | 194 | this.showOnStackBar('') } 197 | /> 198 | 199 | ); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/screens/task-time-log/TaskTimeLogScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { RefreshControl, ScrollView, Text, View } from "react-native"; 5 | import { UsersStack } from "../../navigation/routes"; 6 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 7 | import { TaskTimelog, TaskTimelogRequest, TaskTimelogUser } from "../../lib/models/task/task-time-log"; 8 | import { taskService } from "../../lib/network/http-services/task-service"; 9 | import { HttpError } from "../../lib/network/common/http-error"; 10 | import { CentralSpinner } from "../../lib/components/central-spinner/CentralSpinner"; 11 | import { Button, List, TextInput } from "react-native-paper"; 12 | import { Color } from "../../assets/color"; 13 | import { SnackNotification } from "../../lib/components/snack-notification/SnackNotification"; 14 | 15 | const DEFAULT_TIMELOG = { 16 | id: 0, 17 | name: '', 18 | total: { hours: 0, minutes: 0, seconds: 0 }, 19 | users: [], 20 | error: { status: 0, message: '' } 21 | }; 22 | 23 | interface State { 24 | id: number; 25 | taskTimelog: TaskTimelog; 26 | commentControlValue: string; 27 | hoursControlValue: number; 28 | minutesControlValue: number; 29 | searchOpened: boolean; 30 | snackBarMessage: string; 31 | refreshing: boolean; 32 | httpReqInProcess: boolean; 33 | } 34 | 35 | export class TaskTimeLogScreen extends Component { 36 | 37 | state: State = { 38 | id: 0, 39 | taskTimelog: DEFAULT_TIMELOG, 40 | commentControlValue: '', 41 | hoursControlValue: 0, 42 | minutesControlValue: 0, 43 | searchOpened: false, 44 | snackBarMessage: '', 45 | refreshing: false, 46 | httpReqInProcess: false 47 | }; 48 | 49 | // @ts-ignore 50 | private navigation = this.props.navigation; 51 | 52 | componentDidMount(): void { 53 | this.state.id = this.navigation.state.params.id; 54 | this.requestContent(); 55 | } 56 | 57 | private requestContent(byRefresh?: boolean): void { 58 | this.setState(byRefresh ? { refreshing: true } : { httpReqInProcess: true }); 59 | 60 | taskService.getTimelog(this.state.id) 61 | .then((response: TaskTimelog) => this.processGetResponse(response)) 62 | .catch((error: HttpError) => this.processError(error)) 63 | .finally(() => this.setState({ httpReqInProcess: false, refreshing: false })); 64 | } 65 | 66 | private sendFormData(): void { 67 | this.setState({ httpReqInProcess: true }); 68 | 69 | taskService.addTimelog(this.state.id, this.formData) 70 | .then((response: TaskTimelogRequest) => this.processPostResponse(response)) 71 | .catch((error: HttpError) => this.processError(error)) 72 | .finally(() => this.setState({ httpReqInProcess: false })); 73 | } 74 | 75 | private processGetResponse(response: TaskTimelog): void { 76 | if (response.error) { 77 | this.showOnStackBar(response.error.message); 78 | return; 79 | } 80 | this.setState({ taskTimelog: response }); 81 | } 82 | 83 | private processPostResponse(response: TaskTimelogRequest): void { 84 | if (response.error) { 85 | this.showOnStackBar(response.error.message); 86 | return; 87 | } 88 | this.showOnStackBar('Time successfully logged!'); 89 | const newLog: TaskTimelogUser = { 90 | id: 0, 91 | username: 'You', 92 | timelogged: response.hoursLogged + 'h ' + response.minutesLogged + 'm', 93 | datecreated: 'just', 94 | timecreated: 'the moment' 95 | }; 96 | this.state.taskTimelog.users.push(newLog); 97 | this.setState({ taskTimelog: this.state.taskTimelog }); 98 | } 99 | 100 | private get formData(): TaskTimelogRequest { 101 | // @ts-ignore 102 | return { 103 | userId: 10, 104 | taskId: this.state.id, 105 | comment: this.state.commentControlValue, 106 | hoursLogged: this.state.hoursControlValue, 107 | minutesLogged: this.state.minutesControlValue, 108 | }; 109 | } 110 | 111 | private processError = (error: HttpError) => this.showOnStackBar(error.message); 112 | 113 | private showOnStackBar = (message: string) => this.setState({ snackBarMessage: message }); 114 | 115 | private get description(): string { 116 | if (!this.state.taskTimelog.total) { 117 | return '0 h 0m was logged on task ' + this.state.taskTimelog.name; 118 | } 119 | return (this.state.taskTimelog.total.hours || 0) + 'h ' 120 | + (this.state.taskTimelog.total.minutes || 0) + 'm was logged on task ' 121 | + this.state.taskTimelog.name 122 | } 123 | 124 | private navigateToUserDetails = (id: number): void => this.navigation.navigate(UsersStack.USER_DETAILS, { id: id }); 125 | 126 | render(): ReactNode { 127 | const goBackIcon = { name: 'keyboard-arrow-left', onPress: () => this.navigation.goBack() }; 128 | 129 | return ( 130 | 131 | 132 | 133 | 134 | 135 | 136 | this.setState({ commentControlValue: text }) } 142 | multiline={ true } 143 | /> 144 | 145 | 146 | 147 | this.setState({ hoursControlValue: parseInt(text) || 0 }) } 154 | /> 155 | this.setState({ minutesControlValue: parseInt(text) || 0 }) } 162 | /> 163 | 164 | 175 | 176 | 177 | 178 | this.requestContent(true) } 185 | /> 186 | } 187 | > 188 | 189 | 190 | } 193 | description={ this.description } 194 | > 195 | { this.state.taskTimelog.users.map((user: TaskTimelogUser) => 196 | } 200 | right={ () => ( 201 | 202 | { user.timelogged } 203 | 204 | ) } 205 | onPress={ () => this.navigateToUserDetails(user.id) } 206 | description={ user.datecreated + ' at ' + user.timecreated } 207 | key={ `${ user.id }${ user.timecreated }${ user.timelogged }` } 208 | /> 209 | ) 210 | } 211 | 212 | 213 | 214 | 215 | 216 | this.showOnStackBar('') } 219 | /> 220 | 221 | ); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/screens/create-team/CreateTeamScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { Image, ScrollView, View } from "react-native"; 5 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 6 | import { HttpError } from "../../lib/network/common/http-error"; 7 | import { SnackNotification } from "../../lib/components/snack-notification/SnackNotification"; 8 | import { TeamCreateRequest, TeamCreateResponse } from "../../lib/models/team/team-create"; 9 | import { teamService } from "../../lib/network/http-services/team-service"; 10 | import { Button, Checkbox, List, RadioButton, TextInput } from "react-native-paper"; 11 | import { Color } from "../../assets/color"; 12 | import { UserCandidate } from "../../lib/models/user/user"; 13 | import { InitialsBasedAvatar } from "../../lib/components/icons/initials-based-avatar/InitialsBasedAvatar"; 14 | 15 | interface State { 16 | candidates: UserCandidate[]; 17 | 18 | nameControlValue: string; 19 | leaderControlValue: number; 20 | membersControlValue: number[]; 21 | 22 | snackBarMessage: string; 23 | httpReqInProcess: boolean; 24 | } 25 | 26 | export class CreateTeamScreen extends Component { 27 | 28 | state: State = { 29 | candidates: [], 30 | 31 | nameControlValue: '', 32 | leaderControlValue: 0, 33 | membersControlValue: [], 34 | 35 | snackBarMessage: '', 36 | httpReqInProcess: false 37 | }; 38 | 39 | // @ts-ignore 40 | private navigation = this.props.navigation; 41 | private iconLogoSource = require('../../assets/images/react_cloud_icon/icons8-react-native-filled-150.png'); 42 | 43 | componentDidMount(): void { 44 | this.requestPreCreateData(); 45 | } 46 | 47 | private requestPreCreateData(): void { 48 | this.setState({ httpReqInProcess: true }); 49 | 50 | teamService.getPreCreateData(10) 51 | .then((response: UserCandidate[]) => this.processPreCreateResponse(response)) 52 | .catch((error: HttpError) => this.processError(error)) 53 | .finally(() => this.setState({ httpReqInProcess: false })); 54 | } 55 | 56 | private processPreCreateResponse(response: UserCandidate[]): void { 57 | // @ts-ignore 58 | if (response.error) { 59 | // @ts-ignore 60 | this.showOnStackBar(response.error.message); 61 | return; 62 | } 63 | this.setState({ candidates: response }); 64 | } 65 | 66 | private get anyRequiredFormFieldAbsent(): boolean { 67 | return !this.state.nameControlValue || !this.state.leaderControlValue; 68 | } 69 | 70 | private get formData(): TeamCreateRequest { 71 | return { 72 | projectId: 10, 73 | name: this.state.nameControlValue, 74 | leader: this.state.leaderControlValue, 75 | members: this.state.membersControlValue 76 | }; 77 | } 78 | 79 | private sendFormData(): void { 80 | this.setState({ httpReqInProcess: true }); 81 | 82 | teamService.create(this.formData) 83 | .then((response: TeamCreateResponse) => this.processCreateResponse(response)) 84 | .catch((error: HttpError) => this.processError(error)) 85 | .finally(() => this.setState({ httpReqInProcess: false })); 86 | } 87 | 88 | private processCreateResponse(response: TeamCreateResponse): void { 89 | if (response.error) { 90 | this.showOnStackBar(response.error.message); 91 | return; 92 | } 93 | this.navigation.goBack(); 94 | } 95 | 96 | private processError(error: HttpError): void { 97 | this.showOnStackBar(error.message); 98 | } 99 | 100 | private showOnStackBar(message: string): void { 101 | this.setState({ snackBarMessage: message }) 102 | } 103 | 104 | private leaderChecked(candidateId: number): boolean { 105 | 106 | const whetherLeader = this.state.leaderControlValue === candidateId; 107 | console.log(this.state.leaderControlValue, ' is leader currently ', whetherLeader); 108 | return whetherLeader; 109 | } 110 | 111 | private checkLeader(candidateId: number): void { 112 | console.log('check leader ', candidateId, ' instead of ', this.state.leaderControlValue); 113 | this.setState({ leaderControlValue: candidateId }); 114 | } 115 | 116 | private memberChecked(candidateId: number): boolean { 117 | return this.state.membersControlValue.includes(candidateId); 118 | } 119 | 120 | private checkMember(candidateId: number): void { 121 | let members = this.state.membersControlValue; 122 | 123 | if (this.memberChecked(candidateId)) { 124 | members = members.filter(member => member !== candidateId); 125 | } else { 126 | members.push(candidateId); 127 | } 128 | 129 | this.setState({ membersControlValue: members }); 130 | } 131 | 132 | render(): ReactNode { 133 | const goBackIcon = { name: 'keyboard-arrow-left', onPress: () => this.navigation.goBack() }; 134 | 135 | return ( 136 | 137 | 138 | 139 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | {/* Name */ } 153 | this.setState({ nameControlValue: text }) } 159 | /> 160 | 161 | {/* Team leader radio buttons */ } 162 | } 166 | description="Select leader for your new team." 167 | > 168 | 173 | { this.state.candidates.map(candidate => 174 | } 178 | onPress={ () => this.checkLeader(candidate.id) } 179 | right={ () => 180 | 181 | this.checkLeader(candidate.id) } 185 | /> 186 | 187 | } 188 | key={ candidate.id } 189 | /> 190 | ) 191 | } 192 | 193 | 194 | 195 | {/* Members checkboxes */ } 196 | } 200 | description="Select members for your new team." 201 | > 202 | 207 | { this.state.candidates.map(candidate => 208 | } 212 | onPress={ () => this.checkMember(candidate.id) } 213 | right={ () => 214 | 215 | 218 | 219 | } 220 | key={ candidate.id } 221 | /> 222 | ) 223 | } 224 | 225 | 226 | 227 | 228 | 229 | 240 | 241 | 242 | 243 | 244 | this.showOnStackBar('') } 247 | /> 248 | 249 | ); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/screens/task-details/TaskDetailsScreen.tsx: -------------------------------------------------------------------------------- 1 | import styles from './styles'; 2 | import * as React from "react"; 3 | import { Component, ReactNode } from "react"; 4 | import { View } from "react-native"; 5 | import { TasksStack, UsersStack } from "../../navigation/routes"; 6 | import { ScreenHeader } from "../../lib/components/headers/screen-header/ScreenHeader"; 7 | import { TaskDetails } from "../../lib/models/task/task"; 8 | import { HttpError } from "../../lib/network/common/http-error"; 9 | import { taskService } from "../../lib/network/http-services/task-service"; 10 | import { Chip, Text } from "react-native-paper"; 11 | import { Lookup } from "../../lib/models/common/Lookup"; 12 | import { appPaperTheme } from "../../assets/paper-theme"; 13 | import { Color } from "../../assets/color"; 14 | import { SingleOptionDialog } from "../../lib/components/dialogs/single-option-dialog/SingleOptionDialog"; 15 | import { TaskStatus } from "../../lib/models/task/task-status"; 16 | import { PickerItem } from "react-native-material-dialog"; 17 | import { CentralSpinner } from "../../lib/components/central-spinner/CentralSpinner"; 18 | 19 | const defaultTaskDetails: TaskDetails = { 20 | id: 0, 21 | name: '', 22 | description: '', 23 | dateCreated: '', 24 | timeCreated: '', 25 | status: '', 26 | deadline: '', 27 | parent: { id: 0, name: '' }, 28 | children: [], 29 | createdBy: { id: 0, name: '' }, 30 | assignedBy: { id: 0, name: '' }, 31 | assignedTo: { id: 0, name: '' }, 32 | error: { message: '', status: 200 } 33 | }; 34 | 35 | interface State { 36 | id: number; 37 | statusDialogVisible: boolean; 38 | taskDetails: TaskDetails; 39 | snackBarMessage: string; 40 | httpReqInProcess: boolean; 41 | } 42 | 43 | export class TaskDetailsScreen extends Component { 44 | 45 | state: State = { 46 | id: 0, 47 | statusDialogVisible: false, 48 | taskDetails: defaultTaskDetails, 49 | snackBarMessage: '', 50 | httpReqInProcess: false 51 | }; 52 | 53 | // @ts-ignore 54 | private navigation = this.props.navigation; 55 | 56 | componentDidMount(): void { 57 | this.state.id = this.navigation.state.params.id; 58 | this.requestContent(); 59 | } 60 | 61 | private requestContent(): void { 62 | this.setState({ httpReqInProcess: true }); 63 | 64 | taskService.getDetails(this.state.id) 65 | .then((response: TaskDetails) => this.processGetResponse(response)) 66 | .catch((error: HttpError) => this.processError(error)) 67 | .finally(() => this.setState({ httpReqInProcess: false })); 68 | } 69 | 70 | private processGetResponse(response: TaskDetails): void { 71 | if (response.error) { 72 | this.showOnStackBar(response.error.message); 73 | return; 74 | } 75 | this.setState({ taskDetails: response }); 76 | } 77 | 78 | private processPostResponse(status: PickerItem): void { 79 | this.showOnStackBar('Status successfully changed!'); 80 | this.state.taskDetails.status = status.label; 81 | this.setState({ taskDetails: this.state.taskDetails }); 82 | } 83 | 84 | private processError = (error: HttpError): void => this.showOnStackBar(error.message); 85 | 86 | private showOnStackBar = (message: string) => this.setState({ snackBarMessage: message }); 87 | 88 | private navigateToTimeLog = (id: number): void => this.navigation.navigate(TasksStack.TASK_TIME_LOG, { id: id }); 89 | private navigateToUserDetails = (id: number) => this.navigation.navigate(UsersStack.USER_DETAILS, { id: id }); 90 | private navigateToTaskDetails = (id: number) => this.navigation.navigate(TasksStack.TASK_DETAILS, { id: id }); 91 | 92 | private extractChipId = (lookup?: Lookup) => lookup ? lookup.id : 0; 93 | private extractChipName = (lookup?: Lookup) => lookup ? lookup.name : ''; 94 | 95 | private get assigned(): boolean { 96 | return !!this.state.taskDetails.assignedBy && !!this.state.taskDetails.assignedBy.id; 97 | } 98 | 99 | private get hasParent(): boolean { 100 | return !!this.state.taskDetails.parent && !!this.state.taskDetails.parent.id; 101 | } 102 | 103 | private get hasChildren(): boolean { 104 | return !!this.state.taskDetails.children && !!this.state.taskDetails.children.length; 105 | } 106 | 107 | private get statusValues(): PickerItem[] { 108 | const keys = Object.keys(TaskStatus.value); 109 | // @ts-ignore 110 | return keys.map(key => ({ label: TaskStatus.value[key].label, value: TaskStatus.value[key].id })); 111 | } 112 | 113 | private get currentStatusValue(): PickerItem { 114 | // @ts-ignore 115 | return this.statusValues.find(status => status.value === this.currentStatusId); 116 | } 117 | 118 | private get currentStatusId(): number { 119 | try { 120 | const keys = Object.keys(TaskStatus.value).map(key => key.replace(/_/g, ' ')); 121 | 122 | const key = (keys.find(key => key === this.state.taskDetails.status) as string).replace(/ /g, '_'); 123 | // @ts-ignore 124 | return TaskStatus.value[key].id; 125 | } catch (e) { 126 | return 1; 127 | } 128 | } 129 | 130 | private sendStatus(status: PickerItem): void { 131 | this.closeStatusDialog(); 132 | taskService.setStatus(this.state.id, +status.value, 10) 133 | .then((response: any) => this.processPostResponse(status)) 134 | .catch((error: HttpError) => this.processError(error)) 135 | .finally(() => this.setState({ httpReqInProcess: false })); 136 | } 137 | 138 | private openStatusDialog = () => this.setState({ statusDialogVisible: true }); 139 | private closeStatusDialog = () => this.setState({ statusDialogVisible: false }); 140 | 141 | render(): ReactNode { 142 | const details = this.state.taskDetails; 143 | 144 | const goBackIcon = { name: 'keyboard-arrow-left', onPress: () => this.navigation.goBack() }; 145 | const timelogIcon = { name: 'query-builder', onPress: () => this.navigateToTimeLog(this.state.id) }; 146 | 147 | const chipThemeSun = JSON.parse(JSON.stringify(appPaperTheme)); 148 | chipThemeSun.colors.text = Color.SUN; 149 | 150 | const chipThemeOcean = JSON.parse(JSON.stringify(appPaperTheme)); 151 | chipThemeOcean.colors.text = Color.OCEAN; 152 | 153 | return ( 154 | 155 | 156 | 157 | 158 | 159 | this.sendStatus(selection) } 165 | onCancel={ this.closeStatusDialog } 166 | /> 167 | 168 | 169 | 170 | { details.name } 171 | 172 | Created 173 | { `${ details.dateCreated } at ${ details.timeCreated }` } 174 | 175 | 176 | 177 | 178 | { details.status } 179 | { !!details.deadline && 180 | Deadline: { details.deadline } 181 | } 182 | 183 | 184 | 185 | 186 | Created by 187 | this.navigateToUserDetails(details.createdBy.id) } 191 | >{ details.createdBy.name } 192 | 193 | { 194 | this.assigned && 195 | 196 | Assigned by 197 | this.navigateToUserDetails(this.extractChipId(details.assignedBy)) } 201 | >{ this.extractChipName(details.assignedBy) } 202 | 203 | } 204 | { 205 | this.assigned && 206 | 207 | Assigned to 208 | this.navigateToUserDetails(this.extractChipId(details.assignedTo)) } 212 | >{ this.extractChipName(details.assignedTo) } 213 | 214 | } 215 | 216 | 217 | { this.hasParent && 218 | 219 | 220 | Parent 221 | this.navigateToTaskDetails(this.extractChipId(details.assignedBy)) } 225 | >{ this.extractChipName(details.parent) } 226 | 227 | 228 | } 229 | 230 | { 231 | this.hasChildren && 232 | 233 | 234 | { 235 | !!details.children && details.children.map(child => 236 | 237 | Children 238 | this.navigateToTaskDetails(child.id) } 243 | key={ child.id } 244 | >{ child.name } 245 | 246 | ) 247 | } 248 | 249 | 250 | } 251 | 252 | { !!details.description && 253 | 254 | Description 255 | { details.description } 256 | 257 | } 258 | 259 | 260 | ); 261 | } 262 | } 263 | --------------------------------------------------------------------------------