├── .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 |
3 |
4 | ## View tasks
5 |
6 |
7 | ## Add new ones
8 |
9 |
10 | ## View task details
11 |
12 |
13 | ## Log time
14 |
15 |
16 | ## Change status
17 |
18 |
19 | ## Look up throw status flow
20 |
21 |
22 | ## View users
23 |
24 |
25 | ## Add new ones
26 |
27 |
28 | ## Navigate throw screens
29 |
30 |
31 | ## View teams
32 |
33 |
34 | ## Add new ones
35 |
36 |
37 | ## View profile
38 |
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 |
--------------------------------------------------------------------------------