This commit is contained in:
Martin Donnelly 2016-12-22 00:00:06 +00:00
commit afe73b5baa
236 changed files with 22019 additions and 0 deletions

279
.gitignore vendored Normal file
View File

@ -0,0 +1,279 @@
# Created by https://www.gitignore.io/api/bower,node,dotsettings,visualstudio
### Bower ###
bower_components
.bower-cache
.bower-registry
.bower-tmp
### Node ###
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
jspm_Packages
### DotSettings ###
*.DotSettings
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Ionic
platforms
plugins
www
# AODB Specific
tests/compiled
report
app/core/frame/frame.ts.orig
.vscode/
hooks/after_platform_add/010_install_plugins.js
hooks/
*.orig
.DS_Store
.idea/
jspm_packages/

1
.io-config.json Normal file
View File

@ -0,0 +1 @@
{"app_id":"e5b00de9","api_key":"f7be4e17290bb7e1b87b82db3c86936d948a3d1066502abc"}

11
.project Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>AODB</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>

115
AODB.Mobile.jsproj Normal file
View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Android">
<Configuration>Debug</Configuration>
<Platform>Android</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|iOS">
<Configuration>Debug</Configuration>
<Platform>iOS</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Windows-AnyCPU">
<Configuration>Debug</Configuration>
<Platform>Windows-AnyCPU</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Windows-x64">
<Configuration>Debug</Configuration>
<Platform>Windows-x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Windows-x86">
<Configuration>Debug</Configuration>
<Platform>Windows-x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Windows-ARM">
<Configuration>Debug</Configuration>
<Platform>Windows-ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Windows Phone 8">
<Configuration>Debug</Configuration>
<Platform>Windows Phone 8</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Windows Phone (Universal)">
<Configuration>Debug</Configuration>
<Platform>Windows Phone (Universal)</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Android">
<Configuration>Release</Configuration>
<Platform>Android</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|iOS">
<Configuration>Release</Configuration>
<Platform>iOS</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Windows-AnyCPU">
<Configuration>Release</Configuration>
<Platform>Windows-AnyCPU</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Windows-x64">
<Configuration>Release</Configuration>
<Platform>Windows-x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Windows-x86">
<Configuration>Release</Configuration>
<Platform>Windows-x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Windows-ARM">
<Configuration>Release</Configuration>
<Platform>Windows-ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Windows Phone 8">
<Configuration>Release</Configuration>
<Platform>Windows Phone 8</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Windows Phone (Universal)">
<Configuration>Release</Configuration>
<Platform>Windows Phone (Universal)</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<Service Include="{4a0dddb5-7a95-4fbf-97cc-616d07737a77}" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>f20dd032-ca1f-4eae-b4c3-af8a475fb1a7</ProjectGuid>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0'">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<PropertyGroup>
<TypeScriptToolsVersion>1.6.0</TypeScriptToolsVersion>
<TypeScriptEmitDecoratorMetadata>true</TypeScriptEmitDecoratorMetadata>
</PropertyGroup>
<PropertyGroup>
<TypeScriptCompileOnSaveEnabled>false</TypeScriptCompileOnSaveEnabled>
<TypeScriptEmitDecoratorMetadata>true</TypeScriptEmitDecoratorMetadata>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\ApacheCordovaTools\vs-mda-targets\Microsoft.TypeScript.MDA.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\ApacheCordovaTools\vs-mda-targets\Microsoft.MDA.FileMirroring.targets" />
<PropertyGroup>
<ProjectUISubcaption>Tools for Apache Cordova</ProjectUISubcaption>
</PropertyGroup>
<PropertyGroup>
<TargetPlatformIdentifier>MDD</TargetPlatformIdentifier>
</PropertyGroup>
<Target Name="EnsureBuildPrerequisites">
<!-- These errors will trigger if building from inside Visual Studio and requirements could not be determined -->
<Error Condition="$(MDAPropertiesEvaluated) == 'true' And $(NodeJsDir) == ''" Text="Path to NodeJs could not be determined. Please check that NodeJs has been installed." />
<Error Condition="$(MDAPropertiesEvaluated) == 'true' And $(MDAVsixDir) == ''" Text="Path to the Visual Studio Extension for Tools for Apache Cordova could not be determined. Please check that the extension has been installed." />
<!-- These errors will trigger if building from outside Visual Studio (e.g. command line) and environment variables have not been set -->
<Error Condition="$(MDAPropertiesEvaluated) == '' And $(NodeJsDir) == ''" Text="Path to NodeJs has not been specified. Please check that NodeJs has been installed and set the NodeJsDir environment variable before building." />
<Error Condition="$(MDAPropertiesEvaluated) == '' And $(MDAVsixDir) == ''" Text="Path to Visual Studio Extension for Tools for Apache Cordova has not been specified. Please install it and set the MDAVsixDir environment variable before building." />
<!-- Sanity check that things exist in the specified places. These are more likely to fail if building outside Visual Studio and the required environment variables have not been set, or set incorrectly. -->
<Error Condition="!Exists('$(NodeJsDir)') Or !Exists('$(NodeJsDir)\node.exe')" Text="The specified NodeJs directory $(NodeJsDir) either does not exist, or does not contain node.exe. Please check that NodeJs has been installed, and set the NodeJsDir variable to the correct directory." />
<Error Condition="!Exists('$(MDAVsixDir)') Or !Exists('$(MDAVsixDir)\packages\vs-tac')" Text="The specified directory to the Visual Studio extension $(MDAVsixDir)\node.exe either does not exist, or does not contain a packages\vs-tac sub-directory. Please check that the extension directory exists and set the MDAVsixDir variable to the correct directory." />
<!-- Installs (if necessary) the supporting Nodejs module -->
<Exec Command="&quot;$(MDAVsixDir)&quot;\packages\vs-tac\install &quot;$(NodeJsDir)&quot; &quot;$(MDAVsixDir)&quot;\packages\vs-tac" />
</Target>
<ProjectExtensions>
<VisualStudio>
<UserProperties />
</VisualStudio>
</ProjectExtensions>
<Import Project="_apacheCordovaProjectSourceItems.Targets" Condition="Exists('_apacheCordovaProjectSourceItems.Targets')" />
</Project>

80
AODB.Mobile.sln Normal file
View File

@ -0,0 +1,80 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{262852C6-CD72-467D-83FE-5EEB1973A190}") = "AODB.Mobile", "AODB.Mobile.jsproj", "{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Android = Debug|Android
Debug|iOS = Debug|iOS
Debug|Windows Phone (Universal) = Debug|Windows Phone (Universal)
Debug|Windows Phone 8 = Debug|Windows Phone 8
Debug|Windows-AnyCPU = Debug|Windows-AnyCPU
Debug|Windows-ARM = Debug|Windows-ARM
Debug|Windows-x64 = Debug|Windows-x64
Debug|Windows-x86 = Debug|Windows-x86
Release|Android = Release|Android
Release|iOS = Release|iOS
Release|Windows Phone (Universal) = Release|Windows Phone (Universal)
Release|Windows Phone 8 = Release|Windows Phone 8
Release|Windows-AnyCPU = Release|Windows-AnyCPU
Release|Windows-ARM = Release|Windows-ARM
Release|Windows-x64 = Release|Windows-x64
Release|Windows-x86 = Release|Windows-x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Android.ActiveCfg = Debug|Android
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Android.Build.0 = Debug|Android
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Android.Deploy.0 = Debug|Android
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|iOS.ActiveCfg = Debug|iOS
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|iOS.Build.0 = Debug|iOS
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|iOS.Deploy.0 = Debug|iOS
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows Phone (Universal).ActiveCfg = Debug|Windows Phone (Universal)
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows Phone (Universal).Build.0 = Debug|Windows Phone (Universal)
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows Phone (Universal).Deploy.0 = Debug|Windows Phone (Universal)
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows Phone 8.ActiveCfg = Debug|Windows Phone 8
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows Phone 8.Build.0 = Debug|Windows Phone 8
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows Phone 8.Deploy.0 = Debug|Windows Phone 8
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-AnyCPU.ActiveCfg = Debug|Windows-AnyCPU
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-AnyCPU.Build.0 = Debug|Windows-AnyCPU
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-AnyCPU.Deploy.0 = Debug|Windows-AnyCPU
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-ARM.ActiveCfg = Debug|Windows-ARM
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-ARM.Build.0 = Debug|Windows-ARM
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-ARM.Deploy.0 = Debug|Windows-ARM
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-x64.ActiveCfg = Debug|Windows-x64
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-x64.Build.0 = Debug|Windows-x64
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-x64.Deploy.0 = Debug|Windows-x64
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-x86.ActiveCfg = Debug|Windows-x86
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-x86.Build.0 = Debug|Windows-x86
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Debug|Windows-x86.Deploy.0 = Debug|Windows-x86
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Android.ActiveCfg = Release|Android
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Android.Build.0 = Release|Android
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Android.Deploy.0 = Release|Android
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|iOS.ActiveCfg = Release|iOS
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|iOS.Build.0 = Release|iOS
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|iOS.Deploy.0 = Release|iOS
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows Phone (Universal).ActiveCfg = Release|Windows Phone (Universal)
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows Phone (Universal).Build.0 = Release|Windows Phone (Universal)
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows Phone (Universal).Deploy.0 = Release|Windows Phone (Universal)
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows Phone 8.ActiveCfg = Release|Windows Phone 8
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows Phone 8.Build.0 = Release|Windows Phone 8
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows Phone 8.Deploy.0 = Release|Windows Phone 8
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-AnyCPU.ActiveCfg = Release|Windows-AnyCPU
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-AnyCPU.Build.0 = Release|Windows-AnyCPU
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-AnyCPU.Deploy.0 = Release|Windows-AnyCPU
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-ARM.ActiveCfg = Release|Windows-ARM
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-ARM.Build.0 = Release|Windows-ARM
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-ARM.Deploy.0 = Release|Windows-ARM
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-x64.ActiveCfg = Release|Windows-x64
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-x64.Build.0 = Release|Windows-x64
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-x64.Deploy.0 = Release|Windows-x64
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-x86.ActiveCfg = Release|Windows-x86
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-x86.Build.0 = Release|Windows-x86
{F20DD032-CA1F-4EAE-B4C3-AF8A475FB1A7}.Release|Windows-x86.Deploy.0 = Release|Windows-x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

26
GitVersionConfig.yaml Normal file
View File

@ -0,0 +1,26 @@
assembly-versioning-scheme: MajorMinorPatch
mode: ContinuousDeployment
branches:
master:
tag:
increment: Patch
prevent-increment-of-merged-branch-version: true
release[/-]:
tag: beta
feature[/-]:
tag: useBranchName
increment: Inherit
hotfix[/-]:
tag: beta
support[/-]:
tag:
increment: Patch
prevent-increment-of-merged-branch-version: true
develop:
tag: unstable
increment: Minor
track-merge-target: true
(pull|pull\-requests|pr)[/-]:
tag: PullRequest
increment: Inherit
tag-number-pattern: '[/-](?<number>\d+)[-/]'

25
README.md Normal file
View File

@ -0,0 +1,25 @@
# AODB Mobile Readme
## [Mac OS X Setup Guide](Readme/MacSetupGuide.md)
## [Xubuntu Setup Guide](Readme/XubuntuSetupGuide.md)
## [Development Guide](Readme/DevelopmentGuide.md)
## [Release Guide](Readme/ReleaseGuide.md)
## [Mobile Interface Endpoints](Readme/MobileInterfaceEndpoints.md)
### TODO How do I get set up?
* Summary of set up
* Configuration
* Dependencies
* Database configuration
* How to run tests
* Deployment instructions
Contribution guidelines
* Writing tests
* Code review
* Other guidelines

206
Readme/DevelopmentGuide.md Normal file
View File

@ -0,0 +1,206 @@
# AODB Development
## Background
The AODB Mobile application built using Ionic, Angular and Typescript, it communicates with AODB over a well-defined, loosely coupled interface.
---
## Prerequisites
Access to the team foundation server for the AODB Mobile project which contains the git repository and project documentation.
[Transport AODB Mobile Team Foundation Server](http://i-t-v-tf01:8080/tfs/Transport/Chroma%20Refresh/AODB%20Mobile)
Access to the AODB Mobile project Thycotic Secret Server which contains all account information, usernames and passwords.
[Transport AODB Mobile Thycotic Secret Server](https://secrettransport.pdats.com)
---
## Contents
* [Overview](#overview)
* [Node Packages](#node-packages)
* [Setup and Run](#setup-and-run)
* [Development](#development)
* [Git Flow](#git-flow)
---
## Node Packages
Install the following node packages with specified versions
* gulp
* cordova
* ionic
* jspm
* bower
* npm-check
* npm-install-missing
* phantomjs-prebuilt
* sinopia
* pm2
**Install Node Packages with the following command in the terminal prompt:**
```
$ npm i -g gulp cordova ionic jspm bower npm-check npm-install-missing phantomjs-prebuilt sinopia pm2
```
**List installed Node Packages with the following command in the terminal prompt:**
```
$ npm ls -g --depth=0
```
---
## Setup and Run
The ionic info command prints out useful information about your systems Ionic environment and dependencies
```
$ ionic info
```
Clone the repo
```
$ git clone http://i-t-v-tf01:8080/tfs/Transport/Chroma%20Refresh/_git/AODB
```
Use gulp to install the projects dependencies
```
$ gulp install
```
Use gulp to build the project
```
$ gulp build
```
See the application running run the following command:
```
$ ionic serve -l
```
---
## Development
### TFS
[TFS Conventions Document](http://i-t-v-tf02:12345/ChromaDev/Chroma%20Processes/TFS%20Workflow/TFS%20Conventions.docx)
### Pull requests
>Pull requests let you tell others about changes you've pushed to a repository. Once a pull request is sent, interested parties can review the set of changes, discuss potential modifications, and even push follow-up commits if necessary.
---
### Wallaby.js
>Wallaby.js is an intelligent and super fast test runner for JavaScript that continuously runs your tests. ... Wallaby.js is insanely fast, because it only executes tests affected by your code changes and runs your tests in parallel.
---
### Gulp
>Gulp is a task/build runner for development.
**Gulp watch task will watch all files in the application folder and rebuild the www folder when anything changes**
```
$ gulp watch
```
**Compiles and run the tests**
```
$ gulp test
```
**List Gulp tasks**
```
$ gulp --tasks
```
---
### Git Flow
**Initialize**
**Initialize gitflow**
```
$ git flow init
```
**Features**
**Start a new feature**
```
$ git flow feature start MYFEATURE
```
**Finish up a feature**
```
$ git flow feature finish MYFEATURE
```
**Publish a feature**
```
$ git flow feature publish MYFEATURE
```
**Get a feature published by another user.**
```
$ git flow feature pull origin MYFEATURE
```
**You can track a feature on origin by using**
```
$ git flow feature track MYFEATURE
```
**Releases**
**Start a release**
**To start a release, use the git flow release command**
```
$ git flow release start RELEASE [BASE]
```
**Publish the release branch after creating it to allow release commits by other developers.**
```
$ git flow release publish RELEASE
```
**Track a remote release**
```
$ git flow release track RELEASE
```
**Finish up a release**
**Finishing a release performs several actions:**
* Merges the release branch back into 'master'
* Tags the release with its name
* Back-merges the release into 'develop'
* Removes the release branch
**Finish release**
```
$ git flow release finish RELEASE
```
**Push your tags with**
```
$ git push --tags
```
---
**Start hotfix**
```
$ git flow hotfix start VERSION [BASENAME]
```
**Finish a hotfix**
```
$ git flow hotfix finish VERSION
```
---
**Commands**
| | | | |
|:---------------|:--------------|:-------------|:-------|
| |**init** | | |
|**git flow =>** |**feature =>** |**start =>** |**NAME**|
| |**release** |**finish** | |
| |**hotfix** |**pubish** | |
| | |**pull** | |
---

530
Readme/MacSetupGuide.md Normal file
View File

@ -0,0 +1,530 @@
# AODB Mobile Mac
## Background
This guide is designed to set up and install all the components required for a development, automated build and continual integration environment on Mac OS X.
## Prerequisites
Access to the team foundation server for the AODB Mobile project which contains the git repository and project documentation.
[Transport AODB Mobile Team Foundation Server](http://i-t-v-tf01:8080/tfs/Transport/Chroma%20Refresh/AODB%20Mobile)
Access to the AODB Mobile project Thycotic Secret Server which contains all account information, usernames and passwords.
[Transport AODB Mobile Thycotic Secret Server](https://secrettransport.pdats.com)
Access to the Apple developer account which is used for setting up provisioning profiles, device management and certificates.
[Apple Developer Account](https://developer.apple.com)
---
## Contents
Install and configure the following components:l
* [Rename Computer](#rename-computer)
* [Sophos](#sophos)
* [Java SDK](#java-sdk)
* [Xcode](#xcode)
* [Homebrew](#homebrew)
* [Git](#git)
* [OpenSSL](#openssl)
* [Visual Studio Team Services Agent](#visual-studio-team-services-agent)
* [Node Version Manager](#node-version-manager)
* [Node](#node)
* [Node Packages](#node-packages)
* [Sinopia](#sinopia)
* [PM2 Process Management](#pm2-process-management)
* [WebStorm](#webstorm)
* [SourceTree](#sourcetree)
* [Visual Studio Code](#visual-studio-code)
* [HockeyApp](#hockeyapp)
---
## Rename Computer
>Rename computer name using central technology asset id.
**Asset ID: LM120477**
* Launch System Preferences from the Apple menu in OS X
* Click the Sharing icon
* Type in what you want your Macs new computer name to be
* Close System Preferences for the setting to take effect
---
## Sophos
>Sophos Endpoint doesnt rely on signatures to catch malware, which means it catches zero-day threats without adversely affecting the performance of your device. So you get protection before those exploits even arrive.
Raise Assist with CT for adding Sophos Endpoint
## Java SDK
>The Java Development Kit (JDK) is a software development environment used for developing Java applications and applets. It includes the Java Runtime
Download and install the Java Platform, Standard Edition
[Java SE Downloads](http://www.oracle.com/technetwork/java/javase/downloads/index.html)
**Set $JAVA_HOME environment variable with the following command in the terminal prompt:**
```
$ emacs .profile
```
**Add this to the end of the .profile file:**
```
JAVA_HOME=/Library/Java/Home
export JAVA_HOME;
```
>Save and exit emacs (ctrl-x, ctrl-s; ctrl-x, ctrl-c)
**Confirm Java verion with the following command in the terminal prompt:**
```
$ java -version
```
---
## Xcode
>Xcode is an integrated development environment (IDE) containing the tools for developing iOS.
**Install Xcode with the following command in the terminal prompt:**
```
$ xcode-select --install
```
**Add MobileAppsTeam Developer Account to Xcode**
* Open Xcode
* Xcode > Preferences > Accounts Tab
* Add MobileAppsTeam@leidos.com Account
**iOS developer certificates**
[Apple Developer](https://developer.apple.com/)
* Log into Apple Developer Account with MobileAppsTeam@leidos.com;
* Download iOS developer certificates;
* Open downloaded certificates in Keychain;
* Ensure Xcode has logged has the developer account logged in.
---
## Homebrew
>Homebrew is a open-source software package management.
**Install Homebrew with the following command in the terminal prompt:**
```
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```
**Confirm the Homebrew installed with the following command in the terminal prompt:**
```
$ brew doctor
```
[Homebrew documentation](https://git.io/brew-docs)
---
## Git
>Git is a version control system. Must be version >= 2.9.0
**Install Git with the following command in the terminal prompt:**
```
$ brew install git
```
**Confirm the version of Git with the following command in the terminal prompt:**
```
$ git --version
```
---
## OpenSSL
>OpenSSL is an open source tool for using the Secure Socket Layer (SSL) and Transport Layer Security (TLS) protocols for Web authentication.
**Install OpenSSL with the following command in the terminal prompt:**
```
$ brew install openssl
```
**Ensure folder exists on machine with the following commands in the terminal prompt:**
```
$ mkdir -p /usr/local/lib/
$ ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/
$ ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/
$ brew link --force openssl
$ brew info openssl
```
---
## Visual Studio Team Services Agent
>The Visual Studio Team Services Agent OSX is cross platform build and release agent for Team Services and Team Foundation Server 2015
### Download the OSX agent
[Download vsts-agent-osx.10.11-x64-2.105.7.tar.gz](https://github.com/Microsoft/vsts-agent/releases/download/v2.105.7/vsts-agent-osx.10.11-x64-2.105.7.tar.gz)
**Create OSX agent folder in the `$HOME` with the following command in the terminal prompt:**
```
$ cd $HOME
$ mkdir aodb-vsts-agent && cd aodb-vsts-agent
```
**Extract into agent folder from Downloads with the following command in the terminal prompt:**
```
$ tar xzf ~/Downloads/vsts-agent-osx.10.11-x64-2.105.7.tar.gz
```
### Configure the agent
**Configure the agent with the following command in the terminal prompt:**
```
$ cd $HOME/aodb-vsts-agent
$ ./config.sh
```
**Enter the following details as below:**
Enter Server URL > http://i-t-v-tf01:8080/tfs
Enter authentication type (press enter for Negotive) >
Enter user name > I-T-V-TF01/GITUSER
Enter Password > [Transport AODB Mobile Thycotic Secret Server](https://secrettransport.pdats.com)
Enter queue name > Chroma vNext
Enter agent name > Mac-LM120477
Default any other settings
**Run the agent with the following command in the terminal prompt:**
```
$ ./run.sh
```
**Remove the agent to reconfigure with the following command in the terminal prompt:**
```
$ ./config remove
```
[Visual Studio Team Services Agent Documentation](https://github.com/Microsoft/vsts-agent)
---
## Node Version Manager
>Node Version Manager is bash script to manage multiple active node.js versions
**Install NVM with the following command in the terminal prompt:**
```
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash
```
**Add NVM to the bash_profile with the following command in the terminal prompt:**
```
$ nano ~/.bash_profile
```
**Add the following lines to the .bash_profile :**
export PATH=$PATH:~/.android-sdk-macosx/platform-tools/
export NVM_DIR="/Users/MobileAppsTeam/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
**Verify installation with the following command in the terminal prompt:**
```
$ nvm -v
```
[Node Version Manager Documentation](https://github.com/creationix/nvm#verify-installation)
---
## Node
>Node.js is an open-source, cross-platform JavaScript runtime environment.
**Install Node and NPM with the following command in the terminal prompt:**
```
$ nvm install 6.9.0
$ npm i -g npm@3.10.7
$ nvm use 6.9.0
$ nvm alias default 6.9.0
```
**Verify Node version 6.9.0 and npm version 3.10.7 with the following command in the terminal prompt:**
```
$ node -v && npm -v
```
---
## Node Packages
Install the following node packages with specified versions
* gulp
* cordova
* ionic
* jspm
* bower
* npm-check
* npm-install-missing
* phantomjs-prebuilt
* sinopia
* pm2
**Install Node Packages with the following command in the terminal prompt:**
```
$ npm i -g gulp cordova ionic jspm bower npm-check npm-install-missing phantomjs-prebuilt sinopia pm2
```
**List installed Node Packages with the following command in the terminal prompt:**
```
$ npm ls -g --depth=0
```
---
## Sinopia
>Sinopia is a private / caching npm repository server
**Install Sinopia with the following command in the terminal prompt:**
```
$ npm i sinopia -g
```
**Run Sinopia with the following command in the terminal prompt:**
```
$ sinopia
```
**Set npm registry to Sinopia with the following command in the terminal prompt:**
```
$ npm set registry http://localhost:4873/
```
**Set add user to Sinopia with the following command in the terminal prompt:**
```
$ npm adduser --registry http://localhost:4873/
```
Username: mobileappsteam
Password: [Transport AODB Mobile Thycotic Secret Server](https://secrettransport.pdats.com)
Email: MobileAppsTeam@leidos.com
**Clone local-Chromaux git repository with the following commands in the terminal prompt:**
```
$ cd $HOME/dev
$ git clone http://i-t-v-tf01:8080/tfs/Transport/Chroma%20Refresh/_git/UX
```
**Run the gulp command to export Chroma.UX with the following commands in the terminal prompt:**
```
$ cd $HOME/dev/UX/Chroma.UX/
$ npm install
$ gulp newexport
```
**Check the npm config file has the value registry=http://localhost:4873/ with the following command in the terminal prompt:**
```
$cat ~/.npmrc
```
**Publish the Chroma.UX package to Sinopia with the following commands in the terminal prompt:**
```
$ cd $HOME/dev/UX/local-chromaux/
$ npm publish .
```
[Sinopia Documentation](https://www.npmjs.com/package/sinopia)
---
## PM2 Process Management
>PM2 is a production process manager for Node.js applications with a built-in load balancer. It allows you to keep applications alive forever, to reload them without downtime and to facilitate common system admin tasks.
**Install PM2 with the following command in the terminal prompt:**
```
$ npm i pm2@latest -g
```
**Setup Auto-Completion with the following command in the terminal prompt:**
```
$ pm2 completion >> ~/.bash_profile
```
**Create a process configuration JSON file as ~/process.json**
```json
{
"apps" : [
{
"name" : "Sinopia",
"script" : "/Users/MobileAppsTeam/.nvm/versions/node/v4.4.7/bin/sinopia",
"cwd" : "/Users/MobileAppsTeam",
"watch" : false,
"instances" : 1,
"exec_mode" : "fork",
"combine_logs" : true,
"max_memory_restart" : "300M",
"restart_delay" : 5000
},
{
"name" : "BuildAgent",
"script" : "run.sh",
"cwd" : "/Users/MobileAppsTeam/aodb-vsts-agent",
"watch" : false,
"instances" : 1,
"exec_mode" : "fork",
"combine_logs" : true,
"max_memory_restart" : "300M",
"restart_delay" : 5000
}
]
}
```
**Start with the following command in the terminal prompt:**
```
$ pm2 start ~/profile.json
```
**Additional Commands**
```
$ pm2 kill
$ pm2 monit
$ pm2 list
$ pm2 log 0
$ pm3 log 1
```
[PM2 documentation](http://pm2.keymetrics.io/docs/usage/quick-start/)
---
## Android command line SDK Tools
>SDK Tools is a downloadable component for the Android SDK. It includes the complete set of development and debugging tools for the Android SDK.
**Download the Mac SDK Tools**
[android-sdk_r24.4.1-macosx.zip](https://dl.google.com/android/android-sdk_r24.4.1-macosx.zip)
**Unzip Android command line SDK Tools with the following commands in the terminal prompt:**
```
$ cd ~/Downloads/
$ unzip android-sdk*.zip
$ mv android-sdk-macosx/ ~/.android-sdk-macosx
```
**Run the SDK Manager with the following commands in the terminal prompt:**
```
$ sh ~/.android-sdk-macosx/tools/android
```
**Select the following below:**
Tools
* Android SDK Build-tools 23.03
* Android SDK Build-tools 23.02
* Android SDK Build-tools 23.01
* Android SDK Build-tools 22.01
Android 6.0 (API 23)
* SDK Platform
* Google APIs Intel x86 Atom_64 System Image
* Sources for Android SDK
**Click Install Packages**
**Add platform-tools to your path with the following commands in the terminal prompt:**
```
$ echo 'export PATH=$PATH:~/.android-sdk-macosx/platform-tools/' >> ~/.bash_profile
```
**Refresh your bash profile with the following commands in the terminal prompt:**
```
$ source ~/.bash_profile
```
[Android Developer Website](https://developer.android.com/studio/index.html)
---
## WebStorm
>WebStorm is a JavaScript IDE for client-side development and server-side development with Node.js.
[Download WebStorm for Mac OS](https://download.jetbrains.com/webstorm/WebStorm-2016.2.4.dmg)
[WebStorm Website](https://www.jetbrains.com/webstorm/)
---
## SourceTree
>SourceTree is a Git and Hg client. SourceTree simplifies how you interact with your Git and Mercurial repositories.
[Download SourceTree](https://downloads.atlassian.com/software/sourcetree/SourceTree_2.3.1.zip?_ga=1.107064514.1767012481.1477928157)
---
## Visual Studio Code
>Visual Studio Code is a source code editor developed by Microsoft for Windows, Linux and macOS. It includes support for debugging, embedded Git control, syntax highlighting, intelligent code completion, snippets, and code refactoring.
[Download Visual Studio Code](http://code.visualstudio.com/docs/?dv=osx)
---
## Tinker Tools
>TinkerTool is an application that gives you access to additional preference settings Apple has built into macOS. This allows to activate hidden features in the operating system and in some of the applications delivered with the system
[Download Tinker Tools]()
---
## Brackets
>Brackets is an open source code editor for web designers and front-end developers.
[Download Brackets]()
---
## Chrome
>Chrome is a fast, simple and secure web browser, built for the modern web. Download Chrome.
[Download Chrome]()
---
# TO ADD
screen
How To Use Linux Screen
Control Command
Command: “Ctrl-a”
Switching Between Windows
Command: “Ctrl-a” “n”
Detaching From Screen
Command: “Ctrl-a” “d”
---

View File

@ -0,0 +1,522 @@
# Mobile Interface Endpoints
|Verb|Name|Url|
|---|---|---|
|POST|[Authentication](#authentication)|/api/auth|
|POST|[CancelTransaction](#canceltransaction)|/api/cancelTransaction|
|POST|[ConfirmTransaction](#confirmtransaction)|/api/confirmTransaction|
|POST|[CreateTransaction](#createtransaction)|/api/createTransaction|
|GET|[Detail](#detail)|/api/detail/|
|GET|[Flights](#flights)|/api/flights|
|GET|[GetOperatorImage](#getoperatorimage)|/api/GetOperatorImage|
|GET|[GetTransactionCodes](#gettransactioncodes)|/api/getTransactionCodes|
|GET|[GetTransactionConfig](#gettransactionconfig)|/api/getTransactionConfig|
|GET|[GetUserAccessRightsForTransaction](#getuseraccessrightsfortransaction)|/api/getUserAccessRightsForTransaction|
|GET|[GetWindows](#getwindows)|/api/getWindows|
|POST|[SetSite](#setsite)|/api/setSite|
|GET|[Transactions](#transactions)|/api/transactions|
|POST|[UpdateTransaction](#updatetransaction)|/api/updateTransaction|
|POST|[UpdateFlight](#Updatflight)|/api/updateFlight|
---
## Authentication
**/api/auth**
**Example post**
```json
{
"Username": "username",
"Password": "password",
"ActiveDirectoryUsername": ""
}
```
**Example response**
```json
{
"ContentEncoding": null,
"ContentType": null,
"Data": {
"LoginSuccess": true,
"SiteSelectionRequired": true,
"Sites": {
"SelectedSiteId": 0,
"Sites": [
{
"SiteId": 23,
"SiteName": "Oslo Airport",
"IATACode": "OSL"
},
{
"SiteId": 68,
"SiteName": "Alesund Airport, Vigra",
"IATACode": "AES"
}
],
"Warning": false
}
},
"JsonRequestBehavior": 1,
"MaxJsonLength": null,
"RecursionLimit": null
}
```
---
## CancelTransaction
**Example request**
```
http://localhost:89/api/cancelTransaction
```
**Example post**
```json
{
"Id": "296994",
"PhysflightId": "7126204",
"PublflightId": "7126204",
"Code": "TOW",
"Name": "ACFT TOW",
"Quantity": 1,
"Duration": "0",
"PONumber": "1",
"StartTime": "08/12/2016 12:18",
"EndTime": "08/12/2016 12:18",
"Confirmed": true,
"Cancelled": true,
"CodeType": 1,
"Timestamp": 1689685320
}
```
---
## ConfirmTransaction
**Example request**
```
http://localhost:89/api/confirmTransaction
```
**Example post**
```json
{
"Id": "296994",
"PhysflightId": "7126204",
"PublflightId": "7126204",
"Code": "TOW",
"Name": "ACFT TOW",
"Quantity": 1,
"Duration": "0",
"PONumber": "1",
"StartTime": "08/12/2016 12:18",
"EndTime": "08/12/2016 12:18",
"Confirmed": false,
"Cancelled": true,
"CodeType": 1,
"Timestamp": 1689685320
}
```
**Example response**
```json
{
"Success":true
}
```
---
## CreateTransaction
**Example request**
```
http://localhost:89/api/createTransaction
```
**Example post**
```json
{
"Id": "296994",
"PhysflightId": "7126204",
"PublflightId": "7126204",
"Code": "TOW",
"Name": "ACFT TOW",
"Quantity": 1,
"Duration": "0",
"PONumber": "1",
"StartTime": "08/12/2016 12:18",
"EndTime": "08/12/2016 12:18",
"Confirmed": false,
"Cancelled": false,
"CodeType": 1,
"Timestamp": 1689685320
}
```
**Example response**
```json
{
"Success":true
}
```
---
## Detail
**/api/detail/**
**Example request**
```
http://localhost:89//api/detail/?flightId=7121274&requestId=c9bced9b-1e6e-44b3-a236-0dd3005ee967
```
**Example response**
```json
{
"Editors": [
{
"Name": "readonly",
"Type": "readonly",
"Url": ""
},
{
"Name": "datetime",
"Type": "datetime",
"Url": "updateflightdate"
},
{
"Name": "freetext",
"Type": "freetext",
"Url": "updateflight"
}
],
"Groups": [
{
"Name": "Detail",
"Display": "Details",
"Icon": "ion-plane"
}
],
"Fields": [
{
"Value": "WF",
"Name": "Operator",
"Editor": "readonly",
"Group": "detail",
"Mapping": "SCOPER",
"Restrict": ""
}
],
"Flight": {
"Id": "7121274",
"PhysFlightId": "7121274",
"Type": "D",
"Number": "128",
"Operator": "WF",
"FlightConcat": "WF128",
"AircraftType": "DH1",
"Registration": "",
"Location": "FRO",
"Scheduled": "\/Date(1478635200000)\/",
"Estimated": "\/Date(-62135596800000)\/",
"Actual": "\/Date(-62135596800000)\/",
"Terminal": "T1",
"Stand": ""
},
"IsOutsideOfWindow": false
}
```
---
## Flights
**/api/flights**
>Gets list of flights
**Example request**
```
http://localhost:89/api/flights?window=Default
```
**Example json data**
```json
{
"Flights": [
{
"Id": "6760005",
"PhysFlightId": "6760005",
"Type": "D",
"Number": "4055",
"Operator": "SK",
"FlightConcat": "SK4055",
"AircraftType": "73G",
"Registration": "",
"Location": "SVG",
"Scheduled": "\/Date(1478895600000)\/",
"Estimated": "\/Date(-62135596800000)\/",
"Actual": "\/Date(-62135596800000)\/",
"Terminal": "T1",
"Stand": ""
}
],
"RequestId": "6be3dc32-847c-4d6f-b504-a5dac8444791",
"Filter": "Default"
}
```
---
## GetOperatorImage
**api/GetOperatorImage**
**Example get request**
```
/api/GetOperatorImage?code=BA
```
>Content-Type:image/jpeg
---
## GetTransactionCodes
**/api/getTransactionCodes**
**Example request**
```
http://localhost:89//api/getTransactionCodes
```
**Example response**
```json
[
{
"Code": "TOW",
"CodeType": 1,
"Name": "ACFT TOW"
},
{
"Code": "ASU",
"CodeType": 1,
"Name": "AIR START UNIT"
}
]
```
---
## GetTransactionConfig
**/api/getTransactionConfig**
**Example request**
```
http://localhost:89//api/getTransactionConfig?flightId=7250332&physFlight=7250332
```
**Example response**
```json
[
{
"Id": 0,
"ColumnName": "FLGTTRAN_TRANCATG_CODE",
"Visible": true,
"Editable": true,
"Width": 6,
"Index": 0,
"HeaderText": "Code",
"DateFormat": null,
"Highlight": false,
"Description": "Code",
"Frozen": false,
"Configuration": null,
"ListItems": null,
"Justify": "RIGHT",
"Length": 4,
"Precision": 0,
"ColumnDataType": 0,
"CodeSetType": null,
"LookupType": 0,
"Formatter": null,
"Fixed": false,
"LookupTypeName": null,
"InGrid": false
}
]
```
---
## GetUserAccessRightsForTransaction
**/api/getUserAccessRightsForTransaction**
**Example request**
```
http://localhost:89//api/getUserAccessRightsForTransaction
```
**Example response**
```json
{
"ProfileCode": "FSWH SYSTEM CONTROLLER",
"FunctionDefinition": "FSW_TRANSACTIONS",
"Enabled": true,
"Update": true,
"Add": true,
"Delete": true,
"View": true,
"FuncEnabled": false,
"FuncPackage": false,
"FuncProcedure": false,
"FuncParamaters": 0
}
```
---
## GetWindows
**/api/getWindows**
**Example response**
```json
[
{
"Id": 0,
"Name": "ABB",
"Description": "ABB",
"Type": "STANDARD"
}
]
```
---
//TODO
## SetSite
**/api/setSite**
**Example request**
```
http://localhost:89/api/setSite
```
**Example response**
```json
{
"ContentEncoding": null,
"ContentType": null,
"Data": {
"Error": false
},
"JsonRequestBehavior": 1,
"MaxJsonLength": null,
"RecursionLimit": null
}
```
---
## Transactions
**/api/transactions**
**Example request**
```
http://localhost:89//api/transactions?flightId=7250332&physFlight=7250332
```
**Example response**
```json
[
{
"Id": "296645",
"PhysflightId": "7250332",
"PublflightId": "7250332",
"Code": "TOW",
"Name": "ACFT TOW",
"Quantity": 1,
"Duration": "0",
"PONumber": "",
"StartTime": "10/11/2016 19:14",
"EndTime": "10/11/2016 19:14",
"Confirmed": false,
"Cancelled": false,
"CodeType": 1,
"Timestamp": 1683178617
}
]
```
---
## UpdateTransaction
**Example post**
```json
{
"Id": "296994",
"PhysflightId": "7126204",
"PublflightId": "7126204",
"Code": "TOW",
"Name": "ACFT TOW",
"Quantity": 1,
"Duration": "0",
"PONumber": "1",
"StartTime": "08/12/2016 12:18",
"EndTime": "08/12/2016 12:18",
"Confirmed": false,
"Cancelled": false,
"CodeType": 1,
"Timestamp": 1689685320
}
```
---
## UpdateFlight
**Example post**
```json
{
"ErrorCode": null,
"Error": null,
"Success": true,
"RequiresConfirmation": false,
"ErrorDescription": null,
"ErrorCaption": null,
"PublishedFlightId": "7126204",
"ColumnName": "S1_PHYSFLGT_CREW_NUMBER",
"Value": "100",
"UpdatedValue": "100",
"Operator": null,
"AircraftType": null
}
```
---

97
Readme/ReleaseGuide.md Normal file
View File

@ -0,0 +1,97 @@
# AODB Mobile Release Guide
## Background
## Prerequisites
Access to the team foundation server for the AODB Mobile project which contains the git repository and project documentation.
[Transport AODB Mobile Team Foundation Server](http://i-t-v-tf01:8080/tfs/Transport/Chroma%20Refresh/AODB%20Mobile)
Access to the AODB Mobile project Thycotic Secret Server which contains all account information, usernames and passwords.
[Transport AODB Mobile Thycotic Secret Server](https://secrettransport.pdats.com)
Access to the Apple developer account which is used for setting up provisioning profiles, device management and certificates.
[Apple Developer Account](https://developer.apple.com)
---
## Contents
* [Apple Developer](#apple-developer)
* [HockeyApp](#hockeyapp)
## Apple Developer
>Apple Developer, formerly Apple Developer Connection or ADC, is Apple Inc.'s developer network. It is designed to make available resources to help software developers write software for the Mac OS X and iOS platforms.
[Apple Developer Login](https://developer.apple.com/)
---
## HockeyApp
>HockeyApp is a service for app developers to support them in various aspects of their development process, including the management and recruitment of testers, the distribution of apps and the collection of crash reports.
[HockeyApp Website](https://rink.hockeyapp.net)
### Invite User
* Sign in to HockeyApp
* Click on your app, then on "Invite User".
* Select Role [Roles](#roles)
* User will need to download
**Roles**
* Developers can view and edit all data of the app.
* Members can view all data and answer to feedback messages, but not edit the app or upload builds.
* Testers can download and install the app.
### Export Unprovisioned Devices
* Sign in to HockeyApp
* Click on your app, then on "Users".
* Select "Export > Unprovisioned Devices", then a file will be downloaded.
### Import into Apple Developer
* Sign in to http://developer.apple.com.
* Click on "Certificates, Identifiers & Profiles" in the right sidebar.
* Click on "Devices", then on the + button.
* Select "Register Multiple Devices" and choose the downloaded device file.
* Click on "Continue".
* Confirm the list of imported devices with "Register".
### Download Provisioning Profiles
* iOS Provisioning Profiles (Development)
* Modify your profile contents and select the Generate button to save changes
* Devices
* Click Select all
* Click Generate
* On the Mac Download Provisioning Profiles
* Open Xcode
* Under preference > select account
* Sign in with developer account MobileAppsTeam@leidos.com
* Click "view Details"
* Click "Download" on the latest provisioning profile
* Run ```ls -apl ~/Library/MobileDevice/Provisioning\ Profiles/``` in the terminal
* Select last provisioning id
* Add to build.json provisioningProfile
```json
{
"ios": {
"debug": {
"developmentTeam":"DA3XCEKC7Z",
"provisioningProfile" : "fa3567f0-e841-405a-b9e8-adc652a74f8c"
},
"release": {
"developmentTeam":"DA3XCEKC7Z",
"provisioningProfile" : "fa3567f0-e841-405a-b9e8-adc652a74f8c"
}
}
}
```

418
Readme/XubuntuSetupGuide.md Normal file
View File

@ -0,0 +1,418 @@
# Build Guide Xubuntu
## Background
This guide is designed to set up and install all the components required for a development environment on Xubuntu.
## Prerequisites
Xubuntu operating system
>Xubuntu is an elegant and easy-to-use Unix-like operating system. Xubuntu comes with Xfce, which is a stable, light and configurable desktop environment.
[Download Yakkety Yak the 16.10 xubuntu-16.04-desktop-amd64.iso](http://cdimages.ubuntu.com/xubuntu/releases/16.04/release/xubuntu-16.04-desktop-amd64.iso)
[Xubuntu website](https://xubuntu.org/)
Access to the team foundation server for the AODB Mobile project which contains the git repository and project documentation.
[Transport AODB Mobile Team Foundation Server](http://i-t-v-tf01:8080/tfs/Transport/Chroma%20Refresh/AODB%20Mobile)
Access to the AODB Mobile project Thycotic Secret Server which contains all account information, usernames and passwords.
[Transport AODB Mobile Thycotic Secret Server](https://secrettransport.pdats.com)
---
## Contents
Install and configure the following components:
* [Update and Upgrade](#update-and-upgrade)
* [Sophos](#sophos)
* [Java SDK](#java-sdk)
* [Android command line SDK Tools](#android-command-line-sdk-tools)
* [Development Tools](#development-tools)
* [Node Version Manager](#node-version-manager)
* [Node](#node)
* [Node Packages](#node-packages)
* [Sinopia](#sinopia)
* [PM2 Process Management](#pm2-process-management)
* [WebStorm](#webstorm)
* [Visual Studio Code](#visual-studio-code)
* [Chromium](#chromium)
* [Brackets](#brackets)
---
## Update and Upgrade
>Resynchronize the package index files and Upgrade the Debian Linux system including security update.
**Update and Upgrade with the following command in the terminal prompt:**
```
$ sudo apt-get update
$ sudo apt-get upgrade
```
**Reboot with the following command in the terminal prompt:**
```
$ sudo reboot -n
```
## Sophos
>Sophos Endpoint doesnt rely on signatures to catch malware, which means it catches zero-day threats without adversely affecting the performance of your device. So you get protection before those exploits even arrive.
Raise Assist with CT for adding Sophos Endpoint
---
## Java SDK
>The Java Development Kit (JDK) is a software development environment used for developing Java applications and applets. It includes the Java Runtime
**Use Webup8 Oracle Java8 Installer with the following commands in the terminal prompt:**
```
$ sudo add-apt-repository ppa:webupd8team/java -y
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer
```
**Set Java environment variables with the following command in the terminal prompt:**
```
$ sudo apt-get install oracle-java8-set-default
```
**Check the Java version with the following command in the terminal prompt:**
```
$ java -version
```
**Set environment variables with the following command in the terminal prompt:**
```
$ sudo nano /etc/environment
```
**Add the following to the environment file**
```
JAVA_HOME="/usr/lib/vm/java-8-oracle"
```
**Refresh environment file with the following command in the terminal prompt:**
```
$ source /etc/environment
```
**Set environment variables in .bashrc with the following command in the terminal prompt:**
```
$ sudo nano ~/.bashrc
```
**Add the following to the .bashrc file:**
```
export JAVA_HOME="/usr/lib/vm/java-8-oracle"
```
**Refresh the bash profile with the following commands in the terminal prompt:**
```
$ source ~/.bashrc
```
---
## Android command line SDK Tools
>SDK Tools is a downloadable component for the Android SDK. It includes the complete set of development and debugging tools for the Android SDK.
**Download the Mac SDK Tools**
[android-sdk_r24.4.1-linux.tgz](https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz)
**Unzip Android command line SDK Tools with the following commands in the terminal prompt:**
```
$ mkdir ~/Apps
$ cd ~/Downloads/
$ tar android-sdk*.zip
$ mv android-sdk-linux/ ~/Apps/android-sdk-linux
```
**Add the following to the .bashrc file:**
```
export ANDROID_HOME="$HOME/Apps/android-sdk-linux"
export PATH=$PATH:$ANDROID_HOME/platform-tools/:$ANDROID_HOME/tools/
```
**Refresh your bash profile with the following commands in the terminal prompt:**
```
$ source ~/.bashrc
```
**Run the SDK Manager with the following commands in the terminal prompt:**
```
$ sh ~/.android-sdk-linux/tools/android
```
**Select the following below:**
__Under Tools__
* Android SDK Platform-tools (Rev 25)
* Android SDK Build-tools (Rev 25)
__Under Android 7.11 (API 25)__
* SDK Platform (API 25)
* Google APIs Intel x86 Atom_64 System Image(API 25, Rev 2)
**Click Install Packages**
[Android Developer Website](https://developer.android.com/studio/index.html)
---
## Development Tools
>Build-essential is a package which contains references to numerous packages needed for building software in general
>Git is a version control system that is used for software development and other version control tasks.
>htop is an interactive system-monitor process-viewer. It is designed as an alternative to the Unix program top.
>Linux Screen allows you to: Use multiple shell windows from a single SSH session.
>curl is a command line tool for getting or sending files using URL syntax.
**Install the following packages**
* build-essential
* git
* htop
* screen
* curl
**Run the following command in the terminal prompt:**
```
$ sudo apt-get install build-essential git htop screen curl
```
---
## Node Version Manager
>Node Version Manager is bash script to manage multiple active node.js versions
**Install NVM with the following command in the terminal prompt:**
```
$ wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash
```
**Refresh your bash profile with the following commands in the terminal prompt:**
```
$ source ~/.bashrc
```
**Verify installation with the following command in the terminal prompt:**
```
$ nvm --version
```
[Node Version Manager Documentation](https://github.com/creationix/nvm#verify-installation)
---
## Node
>Node.js is an open-source, cross-platform JavaScript runtime environment.
**Install Node and NPM with the following command in the terminal prompt:**
```
$ nvm install 6.9.0
$ npm i -g npm@3.10.7
$ nvm use 6.9.0
$ nvm alias default 6.9.0
```
**Verify Node version 6.9.0 and npm version 3.10.7 with the following command in the terminal prompt:**
```
$ node -v && npm -v
```
---
## Node Packages
Install the following node packages with specified versions
* gulp
* cordova
* ionic
* jspmw
* bower
* npm-check
* npm-install-missing
* phantomjs-prebuilt
* sinopia
* pm2
**Install Node Packages with the following command in the terminal prompt:**
```
$ npm i -g gulp cordova ionic jspm bower npm-check npm-install-missing phantomjs-prebuilt sinopia pm2
```
**List installed Node Packages with the following command in the terminal prompt:**
```
$ npm ls -g --depth=0
```
---
## Sinopia
>Sinopia is a private / caching npm repository server
**Install Sinopia with the following command in the terminal prompt:**
```
$ npm i sinopia -g
```
**Run Sinopia with the following command in the terminal prompt:**
```
$ sinopia
```
**Set npm registry to Sinopia with the following command in the terminal prompt:**
```
$ npm set registry http://localhost:4873/
```
**Set add user to Sinopia with the following command in the terminal prompt:**
```
$ npm adduser --registry http://localhost:4873/
```
Username: mobileappsteam
Password: [Transport AODB Mobile Thycotic Secret Server](https://secrettransport.pdats.com)
Email: MobileAppsTeam@leidos.com
**Clone local-Chromaux git repository with the following commands in the terminal prompt:**
```
$ cd $HOME/dev
$ git clone http://i-t-v-tf01:8080/tfs/Transport/Chroma%20Refresh/_git/UX
```
**Run the gulp command to export Chroma.UX with the following commands in the terminal prompt:**
```
$ cd $HOME/dev/UX/Chroma.UX/
$ npm install
$ gulp newexport
```
**Check the npm config file has the value registry=http://localhost:4873/ with the following command in the terminal prompt:**
```
$cat ~/.npmrc
```
**Publish the Chroma.UX package to Sinopia with the following commands in the terminal prompt:**
```
$ cd $HOME/dev/UX/local-chromaux/
$ npm publish .
```
[Sinopia Documentation](https://www.npmjs.com/package/sinopia)
---
## PM2 Process Management
>PM2 is a production process manager for Node.js applications with a built-in load balancer. It allows you to keep applications alive forever, to reload them without downtime and to facilitate common system admin tasks.
**Install PM2 with the following command in the terminal prompt:**
```
$ npm i pm2@latest -g
```
**Setup Auto-Completion with the following command in the terminal prompt:**
```
$ pm2 completion >> ~/.bashrc
```
**Create a process configuration JSON file as ~/process.json**
```json
{
"apps" : [
{
"name" : "Sinopia",
"script" : "/home/mobileappsteam/.nvm/versions/node/v4.4.7/bin/sinopia",
"cwd" : "/home/mobileappsteam",
"watch" : false,
"instances" : 1,
"combine_logs" : true,
"max_memory_restart" : "300M",
"restart_delay" : 5000
}
]
}
```
**Generate startup scripts with the following command in the terminal prompt:**
```
$ pm2 startup ubuntu
```
**Following with be generated**
```
PM2] You have to run this command as root. Execute the following command:
sudo su -c "env PATH=$PATH:/home/mobileappsteam/.nvm/versions/node/v6.9.0/bin pm2 startup ubuntu -u mobileappsteam --hp /home/mobileappsteam"
```
**Run the generated command in the terminal prompt:**
```
$ sudo su -c "env PATH=$PATH:/home/mobiledev/.nvm/versions/node/v6.9.0/bin pm2 startup ubuntu -u mobiledev --hp /home/mobiledev"
```
**Save PM2 changes with the following command in the terminal prompt:**
```
$ pm2 save
```
**Start with the following command in the terminal prompt:**
```
$ pm2 start ~/profile.json
```
**Additional Commands**
```
$ pm2 kill
$ pm2 monit
$ pm2 list
$ pm2 log 0
```
[PM2 documentation](http://pm2.keymetrics.io/docs/usage/quick-start/)
---
## WebStorm
>WebStorm is a JavaScript IDE for client-side development and server-side development with Node.js.
[WebStorm Website](https://www.jetbrains.com/webstorm/)
---
## Visual Studio Code
>Visual Studio Code is a source code editor developed by Microsoft for Windows, Linux and macOS. It includes support for debugging, embedded Git control, syntax highlighting, intelligent code completion, snippets, and code refactoring.
[Download Visual Studio Code](http://code.visualstudio.com)
---
## Chromium
>Chromium is the open-source web browser project from which Google Chrome draws its source code.[5] The browsers share the majority of code and features, though there are some minor differences in features and they have different licensing.
**Install Chromium with the following command in the terminal prompt:**
```
$ sudo apt-get install chromium-browser
```
---
## Brackets
>Brackets is an open source code editor for web designers and front-end developers.
**Install Brackets with the following commands in the terminal prompt:**
```
$ sudo add-apt-repository ppa:webupd8team/brackets
$ sudo apt-get update
$ sudo apt-get install brackets
```
---

View File

@ -0,0 +1,75 @@
/**
* ================== angular-ios9-uiwebview.patch.js v1.1.1 ==================
*
* This patch works around iOS9 UIWebView regression that causes infinite digest
* errors in Angular.
*
* The patch can be applied to Angular 1.2.0 1.4.5. Newer versions of Angular
* have the workaround baked in.
*
* To apply this patch load/bundle this file with your application and add a
* dependency on the "ngIOS9UIWebViewPatch" module to your main app module.
*
* For example:
*
* ```
* angular.module('myApp', ['ngRoute'])`
* ```
*
* becomes
*
* ```
* angular.module('myApp', ['ngRoute', 'ngIOS9UIWebViewPatch'])
* ```
*
*
* More info:
* - https://openradar.appspot.com/22186109
* - https://github.com/angular/angular.js/issues/12241
* - https://github.com/driftyco/ionic/issues/4082
*
*
* @license AngularJS
* (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
angular.module('ngIOS9UIWebViewPatch', ['ng']).config(['$provide', function($provide) {
'use strict';
$provide.decorator('$browser', ['$delegate', '$window', function($delegate, $window) {
if (isIOS9UIWebView($window.navigator.userAgent)) {
return applyIOS9Shim($delegate);
}
return $delegate;
function isIOS9UIWebView(userAgent) {
return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
}
function applyIOS9Shim(browser) {
var pendingLocationUrl = null;
var originalUrlFn= browser.url;
browser.url = function() {
if (arguments.length) {
pendingLocationUrl = arguments[0];
return originalUrlFn.apply(browser, arguments);
}
return pendingLocationUrl || originalUrlFn.apply(browser, arguments);
};
window.addEventListener('popstate', clearPendingLocationUrl, false);
window.addEventListener('hashchange', clearPendingLocationUrl, false);
function clearPendingLocationUrl() {
pendingLocationUrl = null;
}
return browser;
}
}]);
}]);

12
app-settings.json Normal file
View File

@ -0,0 +1,12 @@
[
{
"type":"group",
"title":"Common",
"items":[{
"title":"API URL",
"type":"textfield",
"key":"apiurl",
"default": "10.3.90.7"
}]
}
]

View File

@ -0,0 +1,19 @@
import AuthConfig = require('./auth.routes');
import Dectorators = require('../infrastructure/Dectorators/Components');
import authenticationService = require('./services/authentication.service');
import authenticationInterceptor = require('./services/authentication.interceptor');
import LoginComponent = require('./components/login-view/login');
@Dectorators.module('auth')
export class Mod {
constructor(angular: ng.IAngularStatic) {
angular.module(Mod.$componentName, [])
.factory(authenticationService.AuthenticationService.$componentName, authenticationService.AuthenticationService.$factory)
.factory(authenticationInterceptor.AuthenticationInterceptor.$componentName,
authenticationInterceptor.AuthenticationInterceptor.$factory)
.controller(LoginComponent.Controller.$componentName, LoginComponent.Controller)
.directive(LoginComponent.Directive.$componentName, LoginComponent.Directive.$factory)
.config(AuthConfig.Config);
}
}

View File

@ -0,0 +1,27 @@
export class Config {
static $inject: Array<string> = ['$stateProvider',
'$urlRouterProvider',
'$httpProvider'];
constructor($stateProvider: ng.ui.IStateProvider,
$urlRouterProvider: ng.route.IRouteProvider,
$httpProvider: ng.IHttpProvider) {
$stateProvider.state('login', {
url: '/login',
params: {
hasLoggedOut: false,
redirected: false
},
template: '<div class="container">' +
'<chroma:login-view> </chroma:login-view>' +
'</div>'
});
$stateProvider.state('logout', {
url: '/logout'
});
$httpProvider.interceptors.push('authenticationInterceptor');
}
}

View File

@ -0,0 +1,93 @@
#loginbox .panel {
box-shadow: 0 1px 11px rgba(0,0,0,.27);
}
#login-form {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 60px;
.item-input {
width: 100%;
margin-bottom: 8px;
border-width: 0 0 3px 0;
input {
font-size: 110%;
margin-top: -3px;
padding-left: 20px;
color: blue;
}
}
* {
max-width: 300px;
}
}
#logo-text {
/*position:absolute;*/
color: #fbfafa;
font-size: 108%;
margin-left: -10px;
padding-right: 10px;
line-height: 37px;
}
#logo-shape {
position: absolute;
top: 2;
right: 14px;
height: 28px;
width: 100%;
-webkit-transform: skew(24deg);
transform: skew(24deg);
margin-left: -10px;
margin-top: 1px;
z-index: -1;
}
@media (max-width: 360px) and (min-width: 360px) {
#logo-shape {
margin-top: 0px;
}
}
#chroma-text {
display: inline-block;
color: #00398A;
line-height: 37px;
}
#chroma-logo {
}
#chroma-logo-identifier {
display: inline-block;
position: relative;
}
#login-button {
font-size: 22px;
margin-top: 15px;
border: 1px important;
.spinner {
stroke: white;
fill: white;
}
}
#version-string {
font-size: 70%;
}
.floating-prefs {
color: red;
z-index: 1000;
position: absolute !important;
top: 10;
right: 20;
}

View File

@ -0,0 +1,147 @@
import {Mod as CoreModule} from '../../../core/core.mod';
import {Mod as LoginModule} from '../../auth.mod';
import {Directive, Controller} from './login';
import {ComponentTest} from '../../../infrastructure/ComponentHelper';
import {AuthenticationService} from '../../services/authentication.service';
import {Mod as FlightListMod} from '../../../flight-list/flight-list.mod';
import {FlightService, IFlightService} from '../../../core/service/flightService';
import {Mod as SiteSelectionMod} from '../../../site-selection/site-selection.mod';
class LoginViewTest extends ComponentTest {
public api: any;
public $state: ng.ui.IStateService;
public $cookies: ng.cookies.ICookiesService;
public $stateParams: ng.ui.IStateParamsService;
public authenticationService: AuthenticationService;
public flightService: FlightService;
constructor() {
super(Directive.$componentName,
'app/authentication/components/login-view/login.tpl.html');
this.flushOnInit = false;
}
}
describe('Login:', () => {
let instance: LoginViewTest;
let ctrl: Controller;
beforeEach(() => {
if (!instance) {
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('ngCookies');
angular.mock.module('chroma.configuration');
angular.mock.module(CoreModule.$componentName);
angular.mock.module(LoginModule.$componentName);
angular.mock.module(FlightListMod.$componentName);
angular.mock.module(SiteSelectionMod.$componentName);
new CoreModule(angular);
new LoginModule(angular);
new FlightListMod(angular);
new SiteSelectionMod(angular);
// let cookie: ng.cookies.ICookiesService = {}
// cookie['ASP.NET_SessionId'] = 'LOLOLOL';
// let auth: AuthenticationService = new AuthenticationService();
angular.mock.inject((api, $state, $cookies, authenticationService, flightService, siteSelectionService, $stateParams) => {
instance = new LoginViewTest();
instance.api = api;
instance.$state = $state;
instance.$cookies = $cookies;
instance.authenticationService = authenticationService;
instance.flightService = flightService;
instance.$stateParams = $stateParams;
});
}
});
it('Directive is compiled', () => {
instance.flushOnInit = true;
ctrl = instance.compile<Controller>();
expect(ctrl).toBeDefined();
});
//Scenario: Login success with sites to select
//TODO: Ste Oates - I have commented this out because FSWH01 user has access to multiple sites
it('Login success with sites to select', () => {
//SETUP
instance.flushOnInit = false;
instance.$httpBackend.whenPOST(instance.api.authentication).respond({
Data: {
SiteSelectionRequired: true,
Sites: {
Sites: [{ SiteName: 'testSite1', SiteId: '1', IATACode: 'TES' },
{ SiteName: 'testSite2', SiteId: '2', IATACode: 'TEZ' }]
}
}
});
ctrl = instance.compile<Controller>();
//GIVEN that I am logged out of the application
expect(ctrl.isLoggedOut()).toBeTruthy();
//AND I have access to more than one site
//WHEN I enter the correct username and Password
ctrl.username = 'storedUsername';
ctrl.password = 'storedPassword';
spyOn(instance.$state, 'go').and.callFake(function(){ return;});
ctrl.login();
instance.$httpBackend.flush();
//THEN I should be presented with a list of sites to select from
expect(ctrl.sites.length).toBeGreaterThan(1);
expect(ctrl.theresMoreThanOnePossibleSite(ctrl.sites)).toBeTruthy();
expect(instance.$state.go).toHaveBeenCalledWith('chroma.site-selection');
});
//Scenario: Successful logout of the application
it('Logout success', () => {
instance.flushOnInit = false;
ctrl = instance.compile<Controller>();
instance.$cookies['ASP.NET_SessionId'] = 'LOLOLOL';
//GIVEN that I am logged in to the application
spyOn(instance.$state, 'go').and.callThrough();
//ctrl.authenticationService = new AuthenticationService()
expect(ctrl.isLoggedOut()).toBeFalsy();
//WHEN I choose to logout
instance.authenticationService.logout();
//THEN I will be logged out of the application
expect(ctrl.isLoggedOut()).toBeTruthy();
expect(instance.$state.go).toHaveBeenCalledWith('login');
});
it('Session expired then open app then I get re-routed back to login', () => {
instance.flushOnInit = false;
instance.$httpBackend.expectGET(instance.api.flightList + '?window=default').respond(401, {});
ctrl = instance.compile<Controller>();
instance.$cookies['ASP.NET_SessionId'] = 'LOLOLOL';
//GIVEN that my session has expired
//BUT i still have a cookie
expect(ctrl.isLoggedOut()).toBeFalsy();
//WHEN i open the app and get routed to the flight View
spyOn(instance.$state, 'go').and.callThrough();
instance.flightService.getFilter('default', true);
instance.$httpBackend.flush();
//THEN I should be rejected and rerouted back to login
expect(instance.$state.go).toHaveBeenCalledWith('login', {redirected: true});
});
it('should alert the user that their session has timed out if redirected', () => {
instance.flushOnInit = false;
ctrl = instance.compile<Controller>();
instance.$stateParams = {hasLoggedOut: false, redirected: true};
expect(ctrl.error).toEqual('Your session has timed out please login again');
});
});

View File

@ -0,0 +1,71 @@
<ion-view>
<style scoped>
#login {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
.login-container {
background: transparent;
}
.login-container .pane,
.login-container .view {
background-color: transparent !important;
}
</style>
<img id='login' src="res\screen-xhdpi-portrait.png" style="z-index: 0; height: 100%; width: 100%" />
<ion-content scroll="false" class="login-container chr-view-container">
<div class="transcluded">
<ion-nav-view class="relative current-stage">
<div class="pane">
<div class="row view vcenter">
<div class="col ">
<div id="loginbox">
<h1 style="font-size: 39px;" class="text-center alt-font-bold p-accent-color" id="chroma-logo">
<div id="chroma-text">CHROMA</div>
<div id="chroma-logo-identifier">
<div id="logo-text">AODB</div>
<div id="logo-shape" class="primary-background"></div>
</div>
</h1>
<form name="loginForm" role="form" id="login-form">
<label class="search-input item item-input">
<i class="icon ion-ios-person placeholder-icon"></i>
<input id="login-username" type="text" placeholder="username" ng-model="vm.username" required />
</label>
<label class="search-input item item-input">
<i class="icon ion-key rotate-45 placeholder-icon"></i>
<input id="login-password" type="password" placeholder="password" ng-model="vm.password" required />
</label>
<div id="login-error" class="errors full-width text-center" ng-show="vm.error !== undefined">
<i class="icon ion-alert-circled"></i> {{vm.error}}
</div>
<button id="login-button" ng-disabled="vm.status==='loggingIn'" class="action full-width" ng-click="vm.login()">
<ng-if ng-if="vm.status==='loggingIn'">
<ion-spinner icon="spiral" class="vcenter white"></ion-spinner>
</ng-if>
<ng-if ng-if="vm.status==='awaitingLogin'">
Login
</ng-if>
</button>
<div id="version-string" ng-bind="vm.versionString"></div>
<button id="settings-button" ng-disabled="vm.status==='loggingIn'" class="action floating-prefs" ng-click="vm.showSettings()"><i class="icon ion-gear-b"></i></button>
</form>
</div>
</div>
</div>
</div>
</ion-nav-view>
</div>
</ion-content>
</ion-view>

View File

@ -0,0 +1,188 @@
import {controller, directive} from '../../../infrastructure/Dectorators/Components';
import {AuthenticationService, IAuthData, IAuthenticationService} from '../../services/authentication.service';
import {Mod} from '../../auth.mod';
export class Resources {
static USERNAME_MISSING: string = 'Please enter your username';
static PASSWORD_MISSING: string = 'Please enter your password';
static REDIRECTED_DUE_TO_TIMEOUT = 'Your session has timed out please login again';
}
export interface ILoginScope extends ng.IScope {
vm: Controller;
}
export interface ISite {
SiteName: string;
SiteId: string;
IATACode: string;
}
@controller(Mod, 'loginViewController', ['$ionicHistory', 'authenticationService', '$state', '$stateParams', 'siteSelectionService'])
export class Controller {
static cName: string = 'loginViewController';
public error: string;
public sites: Array<ISite>;
public status: string;
public username: string;
public password: string;
public versionString: string;
public prefs: any;
constructor(
private $ionicHistory: any,
private authenticationService: AuthenticationService,
private $state: ng.ui.IStateService,
public $stateParams: ng.ui.IStateParamsService,
public siteSelectionService: any) {
let looseParams: any = <any>this.$stateParams;
let redirected: Boolean = looseParams.redirected;
this.status = 'awaitingLogin';
this.versionString = 'Version: 00000';
this.prefs = (typeof AppPreferences !== 'undefined') ? new AppPreferences() : plugins.appPreferences;
if (typeof cordova !== 'undefined' && typeof cordova.getAppVersion !== 'undefined') {
cordova.getAppVersion.getVersionNumber().then(function (version) {
this.versionString = version;
angular.element($('#version-string')).scope().$apply();
}.bind(this));
}
if (redirected === true) {
this.error = Resources.REDIRECTED_DUE_TO_TIMEOUT;
}
document.addEventListener('resume', function () {
// Cordova resumes
this.retrieveSettings();
}.bind(this), false);
document.addEventListener('deviceready', function () {
this.retrieveSettings();
}.bind(this), false);
}
public login() {
if (this.loginIsValid()) {
this.status = 'loggingIn';
this.authenticationService.authenticate(this.username, this.password).then((response: any) => {
if (response.error !== undefined) {
this.error = response.error;
} else if (response.Data && response.Data.LoginSuccess === false) {
this.error = 'Invalid Username/Password entered';
this.status = 'awaitingLogin';
} else {
if (response.Data.Error !== undefined && response.Data.Error === true) {
this.error = 'something went wrong connecting to the server';
this.status = 'awaitingLogin';
return;
}
if (response.Data.LoginSuccess !== undefined && response.Data.LoginSuccess === false) {
this.error = 'Invalid Username/Password entered';
this.status = 'awaitingLogin';
return;
}
this.sites = response.Data.Sites.Sites;
if (this.theresMoreThanOnePossibleSite(this.sites)) {
this.storeSites(this.sites);
this.$state.go('chroma.site-selection');
} else {
this.$state.go('chroma.flight-list');
}
}
this.status = 'awaitingLogin';
}).catch(() => {
this.error = 'Invalid Username/Password entered';
this.status = 'awaitingLogin';
});
}
}
public retrieveSettings() {
function ok(value) {
this.authenticationService.updateApi(value);
}
function fail(error) {
console.error(error);
}
this.prefs.fetch(ok.bind(this), fail, 'apiurl');
};
public showSettings() {
function fail (error) {
console.error(error);
}
this.prefs.show(null, fail);
}
public theresMoreThanOnePossibleSite(sites: Array<ISite>): Boolean {
if (sites.length > 1) {
return true;
}
return false;
}
public storeSites(sites: Array<ISite>): void {
this.siteSelectionService.storeAvailableSites(sites);
}
public isLoggedOut(): Boolean {
return this.authenticationService.isLoggedOut();
}
public loginIsValid() {
if (!this.username || this.username === '') {
this.error = Resources.USERNAME_MISSING;
return false;
}
if (!this.password || this.password === '') {
this.error = Resources.PASSWORD_MISSING;
return false;
}
return true;
}
}
@directive(Mod, 'chromaLoginView', ['$stateParams'])
export class Directive implements ng.IDirective {
controller: any = Controller.$componentName;
controllerAs: string = 'vm';
templateUrl: string = 'app/authentication/components/login-view/login.tpl.html';
restrict: string = 'E';
scope: any = {};
constructor(private $stateParams: ng.ui.IStateParamsService) {
}
link: ng.IDirectiveLinkFn = (scope: ILoginScope, element: ng.IAugmentedJQuery) => {
let loseParams: any = <any>this.$stateParams;
let hasLoggedOut: Boolean = loseParams.hasLoggedOut;
var loginForm = $('#login-form');
var height = loginForm.height();
loginForm.height(0);
loginForm.css('opacity', 0);
var logo = $('#chroma-logo');
logo.css('opacity', 0);
var animationDuration = 500;
setTimeout(() => {
logo.fadeTo(animationDuration, 1);
setTimeout(() => {
loginForm.animate({ height: height + 30 },
animationDuration, () => {
loginForm.fadeTo(animationDuration, 1);
});
}, animationDuration);
}, 200);
}
}

View File

@ -0,0 +1,19 @@
import {controller, directive, factory} from '../../infrastructure/Dectorators/Components';
import {Mod} from '../auth.mod';
@factory(Mod, 'authenticationInterceptor', ['$q', '$injector'])
export class AuthenticationInterceptor {
constructor(private $q: ng.IQService, private $injector: ng.auto.IInjectorService) {
};
responseError = (rejection) => {
let state: ng.ui.IStateService = this.$injector.get<ng.ui.IStateService>('$state');
if (rejection.status === 401) {
state.go('login', { redirected: true });
return this.$q.reject(rejection);
}
return this.$q.reject(rejection);
}
}

View File

@ -0,0 +1,77 @@
import {controller, directive, factory} from '../../infrastructure/Dectorators/Components';
import {Mod} from '../auth.mod';
export interface IAuthenticationService {
authenticate(username: string, password: string): ng.IPromise<string>;
logout(): void;
}
export interface IAuthData {
error: string;
}
@factory(Mod, 'authenticationService', ['$http', '$q', '$cookies', 'api', '$state'])
export class AuthenticationService implements IAuthenticationService {
constructor(private $http: ng.IHttpService,
private $q: ng.IQService,
private $cookies: ng.cookies.ICookiesService,
private api: any,
private $state: ng.ui.IStateService) {
}
authenticate(username: string, password: string): ng.IPromise<any> {
var data = {
Username: username,
Password: password, ActiveDirectoryUsername: ''
};
return this.$http.post(this.api.authentication, data).then((response: ng.IHttpPromiseCallbackArg<IAuthData>) => {
if (response.data) {
return response.data;
}
}, (error: any) => { return { error: 'Unable to connect to Chroma' }; });
}
isLoggedOut(): Boolean {
if (this.$cookies['ASP.NET_SessionId'] != null) {
return false;
}
return true;
};
logout(): void {
delete this.$cookies['ASP.NET_SessionId'];
this.$state.go('login');
};
updateApi(_newBase:string): void {
let newBase = _newBase;
let validPrefix = /^(ftp|http|https):\/\/[^ "]+$/.test(newBase);
if (!validPrefix) {
newBase = 'http://' + newBase;
}
newBase = newBase.replace(/\/?$/, '');
this.api.endpoint = newBase + '/api/';
this.api.flightList = newBase + '/api/flights';
this.api.authentication = newBase + '/api/auth';
this.api.detail = newBase + '/api/detail/';
this.api.imageSource = newBase + '/api/GetOperatorImage';
this.api.setSite = newBase + '/api/setSite';
this.api.getWindows = newBase + '/api/getWindows';
this.api.transactions = newBase + '/api/transactions';
this.api.cancelTransaction = newBase + '/api/cancelTransaction';
this.api.confirmTransaction = newBase + '/api/confirmTransaction';
this.api.createTransaction = newBase + '/api/createTransaction';
this.api.getTransactionCodes = newBase + '/api/getTransactionCodes';
this.api.getTransactionsAccess = newBase + '/api/getUserAccessRightsForTransaction';
this.api.getTransactionConfig = newBase + '/api/getTransactionConfig';
this.api.signalrHubs = newBase + '/signalr/hubs';
this.api.prmList = newBase + '/api/prmList';
};
}

95
app/chroma.app.ts Normal file
View File

@ -0,0 +1,95 @@
import Auth = require('./authentication/auth.mod');
import Core = require('./core/core.mod');
import FlightList = require('./flight-list/flight-list.mod');
import FlightDetail from './flight-detail/flight-detail.mod';
import SiteSelection = require('./site-selection/site-selection.mod');
import {Mod as WindowList} from './window-list/window-list.mod';
export class App {
static appName: string = 'chroma.app';
dep: Array<string> = ['ui.router',
'ionic',
'ngCookies',
'ngIOS9UIWebViewPatch',
'chroma.templates',
'chroma.configuration',
'chroma.framework'];
mods: Array<string> = [
Core.Mod.$componentName,
Auth.Mod.$componentName,
FlightList.Mod.$componentName,
FlightDetail.$componentName,
SiteSelection.Mod.$componentName,
WindowList.$componentName
];
auth: Auth.Mod;
core: Core.Mod;
flightList: FlightList.Mod;
flightDetail: FlightDetail;
SiteSelection: SiteSelection.Mod;
windowList: WindowList;
constructor(angular: ng.IAngularStatic) {
this.auth = new Auth.Mod(angular);
this.core = new Core.Mod(angular);
this.flightList = new FlightList.Mod(angular);
this.flightDetail = new FlightDetail(angular);
this.SiteSelection = new SiteSelection.Mod(angular);
this.windowList = new WindowList(angular);
angular.module(App.appName, this.dep.concat(this.mods)).config((
$ionicConfigProvider: ionic.utility.IonicConfigProvider,
$compileProvider: ng.ICompileProvider,
$httpProvider: ng.IHttpProvider, $provide: any) => {
$ionicConfigProvider.navBar.alignTitle('center');
$ionicConfigProvider.views.maxCache(0);
$httpProvider.defaults.withCredentials = true;
$compileProvider.debugInfoEnabled(false);
$provide.decorator('$exceptionHandler', ['$delegate', '$injector', ($delegate, $injector) => (exception, cause) => {
$delegate(exception, cause);
let alertBox: ionic.popup.IonicPopupService = $injector.get('$ionicPopup');
let instance: ionic.popup.IonicPopupPromise = alertBox.show({
template: `<div class='row row-center'>
<small class='text-center'>${exception}
</br>
<strong>Restarting the AODB is recommended.</strong></small>
</div>`,
title: 'Unexpected Error',
buttons: [
{
text: 'Continue', onTap: (e) => {
instance.close();
}
},
{
text: '<b>Close App</b>',
type: 'button-assertive',
onTap: function(e) {
let ionicInstance: any = ionic;
ionicInstance.Platform.exitApp();
}
}
]
});
}]);
}).run(function($ionicPlatform) {
$ionicPlatform.registerBackButtonAction(function(e) {
e.preventDefault();
return false;
}, 101);
});
angular.bootstrap(document, [App.appName]);
if (typeof hockeyapp !== 'undefined') {
hockeyapp.start(null, null, '8ca6b61ff4374109b416a476ceb29688');
}
}
}

23
app/core/core.config.ts Normal file
View File

@ -0,0 +1,23 @@
import {ChromaStateService} from './service/chromaStateService';
export class Routes {
static $inject: Array<string> = ['$stateProvider',
'$urlRouterProvider',
'$httpProvider',
'chromaStateProvider'];
constructor($stateProvider: ng.ui.IStateProvider,
$urlRouterProvider: ng.route.IRouteProvider,
$httpProvider: ng.IHttpProvider,
chromaStateProvider: ChromaStateService) {
chromaStateProvider.setStateProvider($stateProvider);
$stateProvider.state('chroma', {
url: '/chroma',
abstract: false,
template: '<chroma:Frame></chroma:Frame>'
});
$urlRouterProvider.otherwise('/login');
}
}

23
app/core/core.mod.ts Normal file
View File

@ -0,0 +1,23 @@
import {module} from '../infrastructure/Dectorators/Components';
import {Routes} from './core.config';
import {FlightService, IFlightService} from './service/flightService';
import {SignalrService, ISignalrService} from './service/signalRService';
import {PushHandlerService, IPushHandlerService} from './service/pushHandler.service';
import {FocusMe} from './service/helpers';
import {ChromaStateService, IChromaStateService} from './service/chromaStateService';
import {Directive as FrameDir, Controller as FrameCtrl} from './frame/frame';
@module('core')
export class Mod {
constructor(angular: ng.IAngularStatic) {
angular.module(Mod.$componentName, [])
.factory(SignalrService.$componentName, SignalrService.$factory)
.factory(PushHandlerService.$componentName, PushHandlerService.$factory)
.provider(ChromaStateService.$componentName, ChromaStateService)
.factory(FlightService.$componentName, FlightService.$factory)
.controller(FrameCtrl.$componentName, FrameCtrl)
.directive(FrameDir.$componentName, FrameDir.$factory)
.directive(FocusMe.$componentName, FocusMe.$factory)
.config(Routes);
}
}

36
app/core/frame/frame.less Normal file
View File

@ -0,0 +1,36 @@
.flight-menu {
background-color: #eaebec;
padding-top: 44px;
a {
background-color: #eaebec;
&.selected {
background-color: white !important;
i {
color: #00398a;
}
}
}
.item {
border: 0 important;
}
i {
font-size: 200%;
color: #5d5d5d;
}
}
.bar {
//background-color: #00398A !important;
border-bottom: 3px solid #F7F1F1;
.title {
color: #00398a;
font-family: Roboto-Thin;
}
}

View File

@ -0,0 +1,113 @@
import {Directive, Controller} from './frame'
import {ComponentTest} from '../../infrastructure/ComponentHelper'
import {Mod as Core} from '../../core/core.mod';
import {Mod as Auth} from '../../authentication/auth.mod';
class FrameViewTest extends ComponentTest {
public $state: ng.ui.IStateService;
public $window: ng.IWindowService;
public $rootScope: ng.IRootScopeService;
public $ionicHistory: ionic.navigation.IonicHistoryService;
constructor() {
super(Directive.$componentName, 'app/core/frame/frame.tpl.html');
}
}
describe('Frame: ', () => {
let test: FrameViewTest,
ctrl: Controller;
beforeEach(() => {
new Core(angular);
new Auth(angular);
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('chroma.configuration');
angular.mock.module('ngCookies');
angular.mock.module(Core.$componentName);
angular.mock.module(Auth.$componentName);
});
beforeEach(() => {
angular.mock.inject(($state, $window, $rootScope, $ionicHistory) => {
test = new FrameViewTest();
test.$state = $state;
test.$window = $window;
test.$rootScope = $rootScope;
test.$ionicHistory = $ionicHistory;
ctrl = test.compile<Controller>();
});
});
it('Directive can be compiled', () => {
expect(ctrl).toBeDefined();
})
it('When no matches, IonicBack is not called', () => {
spyOn(test.$ionicHistory, 'goBack');
ctrl.currentState = 'chroma.undefined-state';
ctrl.back();
expect(test.$ionicHistory.goBack).not.toHaveBeenCalled();
})
it('Logout called if back pressed without state', () => {
spyOn(ctrl.authenticationService, 'logout');
ctrl.back();
expect(ctrl.authenticationService.logout).toHaveBeenCalled();
})
it('Logout called if back pressed on Site Selection state', () => {
spyOn(ctrl.authenticationService, 'logout');
ctrl.currentState = 'chroma.site-selection';
ctrl.back();
expect(ctrl.authenticationService.logout).toHaveBeenCalled();
})
it('Window List called if back pressed on flight List state', () => {
spyOn(test.$state, 'go');
ctrl.currentState = 'chroma.flight-list';
ctrl.back();
expect(test.$state.go).toHaveBeenCalledWith('chroma.window-list');
})
it('Site Selection List called if back pressed on Window List state', () => {
spyOn(test.$state, 'go');
ctrl.currentState = 'chroma.window-list';
ctrl.back();
expect(test.$state.go).toHaveBeenCalledWith('chroma.site-selection');
})
it('Flight List NOT called if back pressed on Flight Detail state and not saved filter', () => {
spyOn(test.$state, 'go');
spyOn(test.$window.sessionStorage, 'getItem').and.returnValue(undefined);
ctrl.currentState = 'chroma.flight-detail';
ctrl.back();
expect(test.$state.go).not.toHaveBeenCalledWith('chroma.flight-list', { filter: 'test-filter' });
})
it('Flight List called if back pressed on Flight Detail state', () => {
spyOn(test.$state, 'go');
spyOn(test.$window.sessionStorage, 'getItem').and.returnValue('test-filter');
ctrl.currentState = 'chroma.flight-detail';
ctrl.back();
expect(test.$state.go)
.toHaveBeenCalledWith('chroma.flight-list', { filter: 'test-filter' });
})
it('Current State is updated on $steChangeStart event', () =>{
let testState : string = 'test-state123';
test.$rootScope.$emit('$stateChangeStart', {name : testState});
expect(ctrl.currentState).toBe(testState);
});
it('isFlightDetailState stays insync with current state', () =>{
let testState : string = 'flight-detail';
test.$rootScope.$emit('$stateChangeStart', {name : testState});
expect(ctrl.currentState).toBe(testState);
expect(ctrl.isFlightDetailState).toBe(true);
});
})

View File

@ -0,0 +1,37 @@
<ion-side-menus enable-menu-with-back-views="true">
<ion-side-menu-content>
<ion-nav-bar>
<ion-nav-buttons side="left">
<button class="button button-icon button-clear ion-ios-arrow-back primary-color" ng-click="vm.back()"></button>
</ion-nav-buttons>
<ion-nav-buttons side="right">
<button ng-show="vm.isFlightDetailState === true" class="button button-icon button-clear ion-android-more-vertical no-animate" menu-toggle="right"></button>
</ion-nav-buttons>
</ion-nav-bar>
<ion-nav-view class="has-header" name="content"></ion-nav-view>
</ion-side-menu-content>
<ion-side-menu side="left" width="200">
<ion-header-bar>
<h1 class="title white">Chroma Mobile</h1>
</ion-header-bar>
<ion-content>
<a ng-click="vm.goToSiteSelection()" class="item" menu-close>
Select Site
</a>
<a ng-click="vm.logout()" class="item" menu-close>
Logout
</a>
</ion-content>
</ion-side-menu>
<ion-side-menu side="right" width="75" is-enabled="vm.isFlightDetailState === true">
<ion-content class="flight-menu">
<a ng-repeat="state in vm.states | filter: {subView:false}" ui-sref="{{state.name}}" class="item text-center" ng-class="{active: state.name === vm.currentState.name}" menu-close>
<i class="{{state.icon}}"></i>
</a>
</ion-content>
</ion-side-menu>
</ion-side-menus>

73
app/core/frame/frame.ts Normal file
View File

@ -0,0 +1,73 @@
import {directive, controller} from '../../infrastructure/Dectorators/Components';
import {IChromaStateService} from '../../core/service/chromaStateService';
import Core = require('../core.mod');
import {AuthenticationService} from '../../authentication/services/authentication.service';
import {IPushHandlerService} from '../../core/service/pushHandler.service';
@directive(Core.Mod, 'chromaFrame', [])
export class Directive implements ng.IDirective {
templateUrl: string = 'app/core/frame/frame.tpl.html';
restrict: string = 'E';
controllerAs: string = 'vm';
controller: string = Controller.$componentName;
scope: any = {};
}
@controller(Core.Mod, 'frameController', ['$rootScope', '$ionicHistory', '$window', '$state', '$ionicPlatform', 'chromaState', 'authenticationService', 'pushHandlerService'])
export class Controller {
public currentState: string;
public states: Array<string>;
public isFlightDetailState: boolean = false;
constructor(
private $rootScope: ng.IRootScopeService,
private $ionicHistory: ionic.navigation.IonicHistoryService,
private $window: ng.IWindowService,
private $state: ng.ui.IStateService,
private $ionicPlatform: ionic.platform.IonicPlatformService,
private chromaState: IChromaStateService,
public authenticationService: AuthenticationService,
private pushHandlerService: IPushHandlerService
) {
this.$rootScope.$on('$stateChangeStart', (event, toState) => {
this.currentState = toState.name;
this.isFlightDetailState = this.IsState('flight-detail');
});
this.states = this.chromaState.states;
this.pushHandlerService.registerForNotifications();
this.$ionicPlatform.onHardwareBackButton((e) => {
e.stopPropagation();
this.back();
});
this.states = this.chromaState.states;
}
public logout(): void {
this.authenticationService.logout();
}
public goToSiteSelection(): void {
this.$state.go('chroma.site-selection');
}
public back(): void {
if (this.currentState === undefined) {
this.logout();
} else if (this.IsState('chroma.site-selection')) {
this.authenticationService.logout();
} else if (this.IsState('chroma.flight-list')) {
this.$state.go('chroma.window-list');
} else if (this.IsState('chroma.window-list')) {
this.$state.go('chroma.site-selection');
} else if (this.IsState('chroma.flight-detail.transaction-detail')) {
this.$state.go('chroma.flight-detail.transaction-list');
} else if (this.IsState('chroma.flight-detail')) {
let filter = this.$window.sessionStorage.getItem('chroma:current-filter');
if (filter) {
this.$state.go('chroma.flight-list', { filter: filter });
}
}
}
private IsState(state: string): boolean {
return this.currentState.indexOf(state) > -1;
}
}

117
app/core/less/aodb.less Normal file
View File

@ -0,0 +1,117 @@
@primary-color: #00398a;
.primary-color {
color: @primary-color !important;
}
.primary-background {
background-color: @primary-color;
}
.white {
color: white !important;
}
.bar .button.button-clear {
color: @primary-color !important;
}
.arrival-col {
color: #fd2424;
}
.departure-col {
color: red;
}
input {
color: #8c8c8c !important;
}
.spinner {
&.primary-color {
stroke: @primary-color;
fill: @primary-color;
}
}
.list-search {
margin: 0 0 25px;
}
button {
&.action {
background-color: transparent;
color: @primary-color !important;
border-color: @primary-color !important;
}
&.action {
&:hover {
background-color: @primary-color !important;
color: white !important;
}
}
}
.pull-right {
float: right;
}
/*.popup {
margin-top: -200px;
}*/
.chroma-list {
.card {
&.ng-enter {
animation: fadeInRight 0.3s;
}
&.ng-leave {
animation: fadeOutLeft 0.3s;
}
&.ng-enter-stagger {
animation-delay: 25ms;
animation-duration: 0;
}
}
.chroma-list-item {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
padding: 15px;
border-color: #F3F1F1;
&.ng-enter {
animation: fadeInRight 0.3s;
}
&.ng-leave {
animation: fadeOutLeft 0.3s;
}
&.ng-enter-stagger {
animation-delay: 25ms;
animation-duration: 0;
}
}
}
.errors {
margin-top: 25px;
background-color: white;
padding: 10px;
border: 1px solid red;
color: red;
}
.card .item:last-child, .list-inset .item:last-child, .padding > .list .item:last-child {
border-radius: 0;
}
.no-animate {
-webkit-transition: none !important;
transition: none !important;
}

View File

@ -0,0 +1,50 @@
import {Mod} from '../core.mod';
import {service} from '../../infrastructure/Dectorators/Components';
export interface IChromaStateService {
states: Array<any>;
addState(name: string, state: IState);
stateProvider: ng.ui.IStateProvider;
}
interface IState extends ng.ui.IState {
display: any;
icon: any;
subView: boolean;
}
@service(Mod, 'chromaState')
export class ChromaStateService implements ng.IServiceProvider {
public stateProvider: ng.ui.IStateProvider;
public states: Array<any> = new Array<any>();
public setStateProvider($stateProvider: ng.ui.IStateProvider) {
this.stateProvider = $stateProvider;
}
public $get(): IChromaStateService {
return {
stateProvider: this.stateProvider,
states: this.states,
addState: (name: string, state: IState) => {
let added = false;
this.states.forEach((s) => {
if (s.name === name) {
added = true;
}
});
if (!added) {
this.states.push({
name: name,
display: state.display,
icon: state.icon,
subView: state.subView
});
this.stateProvider.state(name, state);
}
}
};
}
}

View File

@ -0,0 +1,51 @@
import Dectorators = require('../../infrastructure/Dectorators/Components');
import Core = require('../core.mod');
export interface IFlightListRequest {
Flights: Array<IFlightDetail>;
RequestId: string;
}
export interface IFlightDetail {
Id: string;
PhysFlightId: string;
Type: string;
Number: string;
Operator: string;
AircraftType: string;
Registration: string;
Location: string;
Scheduled: string;
Estimated: string;
Actual: string;
Terminal: string;
Stand: string;
FlightConcat: string;
}
export interface IFlightService {
getFilter(filter: string, broadcast: boolean): ng.IPromise<IFlightListRequest>;
}
@Dectorators.factory(Core.Mod, 'flightService', ['$http', '$q', '$rootScope', 'api'])
export class FlightService implements IFlightService {
constructor(private $http: ng.IHttpService,
private $q: ng.IQService,
private $rootScope: ng.IScope,
private api: any) {
}
public getFilter(filter: string, broadcast: boolean): ng.IPromise<IFlightListRequest> {
let def: ng.IDeferred<IFlightListRequest> = this.$q.defer();
this.$http.get(this.api.flightList + '?window=' + filter).success((data: any, status) => {
def.resolve(data);
if (broadcast) {
this.$rootScope.$broadcast('scroll.refreshComplete');
}
});
return def.promise;
}
}

View File

@ -0,0 +1,16 @@
import Dectorators = require('../../infrastructure/Dectorators/Components');
import Core = require('../core.mod');
@Dectorators.directive(Core.Mod, 'focusMe', ['$timeout'])
export class FocusMe implements ng.IDirective {
restrict: string = 'EA';
scope: any = {};
constructor(private $timeout: ng.ITimeoutService) {
}
link: ng.IDirectiveLinkFn = (scope, element, attrs) => {
this.$timeout(() => {
element[0].focus();
}, 750);
}
}

View File

@ -0,0 +1,60 @@
import {factory} from '../../infrastructure/Dectorators/Components';
import {Mod} from '../../core/core.mod';
export interface IPushHandlerService {
registerForNotifications();
getDeviceToken();
}
@factory(Mod, 'pushHandlerService', ['$window'])
export class PushHandlerService implements IPushHandlerService {
constructor(private $window : ng.IWindowService) {
}
public registerForNotifications() {
if (PushNotification !== undefined) { // we are on the emulator otherwise
let push = PushNotification.init({
'android': {
'senderID': '194436060542',
'icon': 'icon-96-xhdpi-TransparentText.png',
'iconColor': 'grey'
},
'ios': {
'alert': 'true',
'badge': 'true',
'sound': 'true'
}
});
push.on('registration', this.onRegistered);
push.on('notification', this.onNotification);
push.on('error', this.onError);
}
}
private onNotification(data) {
//alert('got notification ' + data.additionalData.alert);
return true;
}
private onRegistered = (data) => {
// alert('Got token ' + data.registrationId);
this.storeDeviceToken(data.registrationId);
}
private onError(e) {
// alert('push error ' + e.message);
}
public getDeviceToken(): string {
return this.$window.sessionStorage.getItem('chroma:device-token');
}
public storeDeviceToken(deviceToken: string) {
this.$window.sessionStorage.setItem('chroma:device-token', deviceToken);
}
}

View File

@ -0,0 +1,63 @@
import {factory} from '../../infrastructure/Dectorators/Components';
import {Mod} from '../core.mod';
import {IPushHandlerService} from './pushHandler.service';
export interface ISignalrService {
start(jq: any, callbackMethod: any, scope: any);
}
@factory(Mod, 'signalrService', ['api', 'pushHandlerService', '$ionicPlatform'])
export class SignalrService implements ISignalrService {
constructor(private api: any,
private pushHandlerService: IPushHandlerService,
private $ionicPlatform: any) {
}
proxy: any;
callbackMethod: any;
createHub(jq : any, scope: any) {
//let jq = <any>$;
var connection = jq.hubConnection(this.api.signalrHubs, {useDefaultPath: false});
var mobileHubProxy = connection.createHubProxy('mobileHub');
connection.logging = true;
mobileHubProxy.on('recieveHandlingTransactionAdd', (message) => {
console.log('transaction recieved' + 'name: ' + name + 'mesage: ' + message);
this.callbackMethod('recieveHandlingTransactionAdd', scope, message);
});
mobileHubProxy.on('recieveHandlingTransactionCancel', (message) => {
console.log('transaction cancelled' + 'name: ' + name + 'mesage: ' + message);
this.callbackMethod('recieveHandlingTransactionCancelled', scope, message);
});
connection.start().done(() => {
var platform = undefined;
if (this.$ionicPlatform.is('android')) {
platform = 1;
}
if (this.$ionicPlatform.is('ios')) {
platform = 0;
}
mobileHubProxy.invoke('mobileRegister', platform, this.pushHandlerService.getDeviceToken());
});
}
public start(jq : any, callbackMethod: any, scope: any) {
this.createHub(jq, scope);
this.callbackMethod = callbackMethod;
}
}

View File

@ -0,0 +1,67 @@
.value-holder {
width: 50%;
}
.readonly[disabled] {
background-color: transparent !important;
color: black !important;
}
.flight-container {
font-size: 115%;
background-color: white !important;
box-shadow: -1px 1px 1px 1px rgb(234, 232, 232);
padding: 3px 0 !important;
.header-row {
font-size: 130%;
.a-d-ind {
font-size: 110%;
margin-top: -3px;
}
}
.concat {
margin-top: 15px;
font-size: 105%;
}
.description {
}
.field.col,
.field .row {
padding-top: 0;
}
}
.group-list {
margin-top: 5px;
.editor-field {
background-color: transparent !important;
.input-label {
width: 55%;
color: #A09F9F;
}
input {
text-overflow: ellipsis;
}
input[disabled] {
background-color: transparent !important;
color: black !important;
}
}
}
.rotate-45 {
transform: rotate(45deg);
}
.rotate-225 {
transform: rotate(225deg);
}

View File

@ -0,0 +1,162 @@
import {ComponentTest} from '../../infrastructure/ComponentHelper';
import {Directive as DetailDir, Controller as DetailCtrl, FlightDetailParams} from './flight-detail';
import DetailMod from '../flight-detail.mod';
import {Mod as CoreMod} from '../../core/core.mod';
import {IFlightInformationModel} from '../services/flightInformationService';
import {IFlightDetail} from '../../core/service/flightService';
class FlightDetailTest extends ComponentTest {
constructor() {
super(DetailDir.$componentName, 'app/flight-detail/detail/flight-detail.tpl.html')
}
}
class TestInstance {
public test: FlightDetailTest;
public api: any;
public $httpBackend: ng.IHttpBackendService;
public $state: ng.ui.IStateService;
public $stateParams: ng.ui.IStateParamsService;
public $rootScope: ng.IRootScopeService;
constructor() {
angular.mock.inject(($httpBackend, $state, $stateParams, $rootScope, api) => {
this.$httpBackend = $httpBackend;
this.$state = $state;
this.$stateParams = $stateParams;
this.$rootScope = $rootScope;
this.api = api;
});
}
public getSelectedFlight(): IFlightDetail {
return {
Id: '1', Type: 'A', Operator: 'SK', Number: '0214', AircraftType: '333',
Registration: 'PK-GPE', Location: 'MAN', Scheduled: '09:00', Estimated:
'09:00', Actual: '09:00', Terminal: 'T1', Stand: 'A1', FlightConcat: 'SK0214'
};
}
public setupForListGet(): IFlightInformationModel {
let model: IFlightInformationModel = {
Flight: undefined,
Groups: [{ "Name": "Detail", "Display": "Details", "Icon": "ion-navicon" }],
Fields: undefined,
Editors: undefined,
IsOutsideOfWindow: false
};
this.$httpBackend.expectGET(`${this.api.detail}?flightId=${this.getSelectedFlight().Id}&requestId=ste1234567`).respond(201, model);
return model;
}
}
describe('Flight Details:', () => {
let instance: TestInstance;
beforeEach(() => {
new DetailMod(angular);
new CoreMod(angular);
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('chroma.configuration')
angular.mock.module(DetailMod.$componentName);
angular.mock.module(CoreMod.$componentName);
instance = new TestInstance();
instance.test = new FlightDetailTest();
});
describe('Framework: ChromaDateFilter', () => {
let $filter: any;
beforeEach(() => {
angular.mock.inject((_$filter_) => {
$filter = _$filter_;
});
});
it('ChromaDateFilter: If input is empty, return empty', () => {
let result = $filter('chromaDateFilter')('', false);
expect(result).toBe('');
});
it('ChromaDateFilter: If input is default DateTime, return empty', () => {
let result = $filter('chromaDateFilter')('/Date(-62135596800000)/', false);
expect(result).toBe('');
});
it('ChromaDateFilter: return HH:mm if not a Date request', () => {
let result = $filter('chromaDateFilter')('/Date(1447243800000)/', false);
expect(result).toBe('12:10');
});
it('ChromaDateFilter: return [DD] HH:mm if a Date request', () => {
let result = $filter('chromaDateFilter')("/Date(1447243800000)/", true);
expect(result).toBe('[11] 12:10');
});
});
describe('Scenario: Viewing flights by window', () => {
//Given that I have selected a window
// And select a flight that I want to View
//Then I should see the flights information
//Grouped by category
it('Groups should be displayed', function () {
let model: IFlightInformationModel,
ctrl: DetailCtrl,
params: FlightDetailParams,
baseStateName: string,
expectedStateName: string;
params = <FlightDetailParams>instance.$stateParams;
params.flight = instance.getSelectedFlight();
params.requestId = 'ste1234567';
baseStateName = 'chroma.flight-detail.';
spyOn(instance.$state, "go").and.callThrough();
model = instance.setupForListGet();
ctrl = instance.test.compile<DetailCtrl>()
expectedStateName = baseStateName + model.Groups[0].Name;
expect(instance.$state.go).toHaveBeenCalledWith(expectedStateName);
})
it('View title updated on navigation', function () {
let model: IFlightInformationModel,
ctrl: DetailCtrl,
params: FlightDetailParams,
newGroup: string = "NewGroup123";
params = <FlightDetailParams>instance.$stateParams;
params.flight = instance.getSelectedFlight();
params.requestId = 'ste1234567';
model = instance.setupForListGet();
ctrl = instance.test.compile<DetailCtrl>()
instance.$rootScope.$emit('flightGroupChanged', { Display: newGroup })
expect(ctrl.viewTitle).toBe(newGroup);
})
})
})

View File

@ -0,0 +1,64 @@
<ion-view view-title="{{vm.viewTitle}}">
<ion-content scroll="false">
<div ng-show="vm.model.IsOutsideOfWindow" class="row row-center">
<h4 class="col text-center">
<div class="font-thin primary-color">This flight is outside of the selected Flight Window and is no longer viewable.</div>
<a class="primary-color" ng-click="vm.goBack();">Go back</a>
</h4>
</div>
<div ng-show="!vm.model.IsOutsideOfWindow">
<div class="row" ng-show="vm.loading && !vm.model.IsOutsideOfWindow">
<div class="col">
<ion-spinner icon="spiral" class="primary-color vcenter"></ion-spinner>
</div>
</div>
<div class="flight-container flight-detail flight-header" ng-show="!vm.loading && !vm.model.IsOutsideOfWindow">
<div class="row">
<div class="col-20">
<a class="item-avatar" on-tap="vm.showDetail(flt)">
<img ng-src="{{vm.flightInformationService.getOperatorUrl(vm.flight.Operator);}}">
</a>
</div>
<div class="col item" style="margin-top: -10px;">
<div class="row">
<h2 class="col">
<span class="primary-color">{{vm.flight.Operator}}</span>{{vm.flight.Number}}
<span class="primary-color">/</span> {{vm.flight.Location}}
<span class="primary-color">/</span> {{vm.flight.Registration}}
<span class="pull-right" ng-switch="vm.flightInformationService.getTimeDisplayEnum(vm.flight)">
<span ng-switch-when="A">A: {{vm.flight.Actual | chromaDateFilter}}</span>
<span ng-switch-when="E">E: {{vm.flight.Estimated | chromaDateFilter}}</span>
<span ng-switch-when="S">S: {{vm.flight.Scheduled | chromaDateFilter}}</span>
</span>
</h2>
</div>
<div class="row" style="margin-top: 2px;">
<div class="col text-left primary-color shunt-down-text">
<span class="descriptor"><i class="icon ion-ios-paperplane"></i></span> {{vm.flight.AircraftType}}
</div>
<div class="col text-left primary-color shunt-down-text">
<span class="descriptor"><i class="icon ion-speakerphone"></i> </span> {{vm.flight.Terminal}}
</div>
<div class="col text-left primary-color shunt-down-text">
<span class="descriptor"><i class="icon ion-ios-pricetag"></i> </span> {{vm.flight.Stand}}
</div>
<div class="col text-right" ng-class="{arrival: vm.flight.Type === 'A', departure: vm.flight.Type === 'D'}">
<div class="ind-text text-center" ng-if="vm.flight.Type === 'A'">
<small>Inbound</small>
</div>
<div class="ind-text text-center" ng-if="vm.flight.Type === 'D'">
<small>Outbound</small>
</div>
</div>
</div>
</div>
</div>
</div>
<ion-nav-view class="animated fadeInRight" style="margin-top: 15px; height: 72%;" ng-show="!vm.loading" name="flight-detail"></ion-nav-view>
</div>
</ion-content>
</ion-view>

View File

@ -0,0 +1,169 @@
import {controller, directive} from '../../infrastructure/Dectorators/Components';
import {FlightListParams} from '../../flight-list/list/flight-list';
import {IFlightDetail} from '../../core/service/flightService';
import {IChromaStateService} from '../../core/service/chromaStateService';
import {IFlightInformationService, IFlightInformationModel, IFlightInformationGroup, IUserAccessModel} from
'../services/flightInformationService';
import Mod from '../flight-detail.mod';
export interface FlightDetailParams extends FlightListParams {
flight: IFlightDetail;
requestId: string;
}
@controller(Mod, 'flightDetailController', ['$stateParams', '$state', '$rootScope', '$window', 'chromaState', 'flightInformationService'])
export class Controller {
public flight: IFlightDetail;
public model: IFlightInformationModel;
public loading: boolean = true;
public viewTitle: string;
constructor(private $stateParams: FlightDetailParams,
private $state: ng.ui.IStateService,
private $rootScope: ng.IRootScopeService,
private $window: ng.IWindowService,
private chromaState: IChromaStateService,
private flightInformationService: IFlightInformationService) {
this.getFlight(true);
this.$rootScope.$on('flightGroupChanged', (event: ng.IAngularEvent, group: IFlightInformationGroup) => {
this.viewTitle = group.Display;
});
this.$rootScope.$on('chroma:flight-updated', () => {
this.getFlight(false);
});
}
private getFlight(buildGroups: Boolean): void {
this.flightInformationService.getFlight(this.$stateParams.flight, this.$stateParams.requestId)
.then((m: IFlightInformationModel) => {
this.flight = m.Flight;
this.buildView(m, buildGroups);
this.flightInformationService.getTransactionAccess()
.then((tranAccess: IUserAccessModel) => {
if (tranAccess.Enabled && tranAccess.View) {
this.addTransactionState(tranAccess);
}
});
this.addPRMState();
});
}
private addTransactionState(tranAccess: IUserAccessModel) {
this.chromaState.addState('chroma.flight-detail.transaction-list', {
url: '/transaction-list',
display: 'Transactions',
icon: 'ion-social-usd',
subView: false,
params: {
tranAccessModel: tranAccess
},
views: {
'flight-detail': {
template: `<chroma:Transaction-List model='vm.model.Flight'></chroma:Transaction-List>`
}
}
});
this.chromaState.addState('chroma.flight-detail.transaction-detail', {
url: '/transaction-detail',
display: 'Transaction Detail',
icon: '',
subView: true,
params: {
transaction: null,
tranAccess: undefined,
columnConfig: undefined
},
views: {
'flight-detail': {
template: `<chroma:Transaction-Detail model='vm.model.Flight'></chroma:Transaction-Detail>`
}
}
});
}
private addPRMState() {
this.chromaState.addState('chroma.flight-detail.prm-list', {
url: '/prm-list',
display: 'Prms',
icon: 'ion-social-tux',
subView: false,
views: {
'flight-detail': {
template: `<chroma:Prm-List model='vm.model.Flight'></chroma:Prm-List>`
}
}
});
this.chromaState.addState('chroma.flight-detail.prm-detail', {
url: '/prm-detail',
display: 'PRM Detail',
icon: '',
subView: true,
params: {
prmList: null
},
views: {
'flight-detail': {
template: `<chroma:Prm-Detail></chroma:Prm-Detail>`
}
}
});
}
private goBack(): void {
let previousWindow: string =
this.$window.sessionStorage.getItem('chroma:current-filter');
this.$state.go('chroma.flight-list', { filter: previousWindow });
}
private buildView(model: IFlightInformationModel, buildGroups: Boolean): void {
if (model.IsOutsideOfWindow) {
this.loading = false;
this.model = model;
return;
}
if (buildGroups && model.Groups && model.Groups.length > 0 && !model.IsOutsideOfWindow) {
model.Groups.forEach((grp: IFlightInformationGroup) => {
this.chromaState.addState('chroma.flight-detail.' + grp.Name, {
url: '/' + grp.Name,
display: grp.Display,
icon: grp.Icon,
subView: false,
params: {
group: grp
},
views: {
'flight-detail': {
template: `<chroma:Flight-Detail-Group model='vm.model'></chroma:Flight-Detail-Group>`
}
}
});
});
}
this.model = model;
if (buildGroups) {
this.$state.go(this.chromaState.states[0].name);
}
this.loading = false;
this.$rootScope.$broadcast('scroll.refreshComplete');
}
}
@directive(Mod, 'chromaFlightDetail')
export class Directive implements ng.IDirective {
controller: string = Controller.$componentName;
controllerAs: string = 'vm';
templateUrl: string = 'app/flight-detail/detail/flight-detail.tpl.html';
restrict: string = 'E';
replace: boolean = false;
scope: any = true;
}

View File

@ -0,0 +1,48 @@
import * as InformationService from '../services/flightInformationService';
export interface IUpdateResonse {
Success: boolean;
Error: string;
}
export class BaseEditorController {
public field: InformationService.IFlightInformation;
public model: InformationService.IFlightInformationModel;
public definition: InformationService.IFlightEditor;
public instance: ionic.popup.IonicPopupPromise;
public status: string;
public error: string;
public updateUrl: string;
constructor(private $http: ng.IHttpService,
private $rootScope: ng.IRootScopeService,
private $timeout: ng.ITimeoutService,
private api: any) {
this.updateUrl = this.api.endpoint + this.definition.Url;
}
public updateValue(requestParams: any): void {
if (this.status === 'E' || this.status === 'S' || this.updateUrl === '') {
this.instance.close();
return;
}
this.status = 'P';
this.$http.post(this.updateUrl, requestParams).success((response: IUpdateResonse) => {
this.handleUpdateResult(response, requestParams);
this.$rootScope.$emit('chroma:flight-updated');
});
}
private handleUpdateResult(response: IUpdateResonse, request: any): void {
if ((response && response.Success) || (request.IsDelete && response)) {
this.status = 'S';
this.$timeout(() => {
this.instance.close();
}, 600);
} else {
this.status = 'E';
this.error = response.Error;
}
}
}

View File

@ -0,0 +1,5 @@
<chroma:Datetime-Editor definition="editor"
field="field"
instance="instance"
model="model">
</chroma:Datetime-Editor>

View File

@ -0,0 +1,182 @@
import {ComponentTest} from '../../../infrastructure/ComponentHelper';
import DetailMod from '../../flight-detail.mod';
import {Mod as CoreMod} from '../../../core/core.mod';
import {Directive, Controller} from './datetime';
import * as InformationService from "../../services/flightInformationService";
class DatetimeEditorTest extends ComponentTest {
public compileString: string = `<chroma:Datetime-Editor definition="definition" field="field"instance="instance"model="model"> </chroma:Datetime-Editor>`;
constructor() {
super(Directive.$componentName, 'app/flight-detail/editors/datetime/datetime.tpl.html');
}
public getTestingScope(): any {
return {
field: {
Value: '',
Name: 'Crew',
Editor: 'datetime',
Group: 'Details',
Mapping: 'SCCREW'
},
definition: { Url: 'testURL' },
model: {
Flight: { Id: '0001' }
}, instance: {
close() { }
}
};
}
public setupForRequest(scope: any, responseObj: any, afterCtrlBuld: any = undefined, beforeUpdate: any = undefined): Controller {
let ctrl = this.compile<Controller>(scope, this.compileString);
if (afterCtrlBuld) {
afterCtrlBuld(ctrl);
}
this.$httpBackend.expectPOST(ctrl.updateUrl).respond(201, responseObj);
if (beforeUpdate) {
beforeUpdate(ctrl);
}
ctrl.update();
this.$httpBackend.flush();
return ctrl;
}
}
describe('Datetime Editor:', () => {
let instance: DatetimeEditorTest,
detailMod: DetailMod,
coreMod: CoreMod;
beforeEach(() => {
detailMod = new DetailMod(angular);
coreMod = new CoreMod(angular);
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('chroma.configuration');
angular.mock.module(DetailMod.$componentName);
angular.mock.module(CoreMod.$componentName);
instance = new DatetimeEditorTest();
});
describe('Framework:', () => {
it('Compiles with required properties', () => {
let ctrl: Controller,
scope: any;
scope = instance.getTestingScope();
ctrl = instance.compile<Controller>(scope, instance.compileString);
expect(ctrl).toBeDefined();
expect(ctrl.field).toBeDefined();
expect(ctrl.definition).toBeDefined();
expect(ctrl.model).toBeDefined();
expect(ctrl.instance).toBeDefined();
});
});
describe('Behaviour', () => {
it('Malformed time will display error', () =>{
let ctrl: Controller, scope: any, valid: boolean;
scope = instance.getTestingScope();
ctrl = instance.compile<Controller>(scope, instance.compileString);
ctrl.update();
expect(ctrl.error).toBe('Date/Time not in the correct format');
});
it('Time value displayed as empty if field has no value', () => {
let ctrl: Controller,
scope: any;
scope = instance.getTestingScope();
ctrl = instance.compile<Controller>(scope, instance.compileString);
expect(ctrl.wrapper.timeValue).toBe(undefined);
})
it('Time value displayed as 09:40 with 13/11/2015 09:40:00', () => {
let ctrl: Controller,
scope: any;
scope = instance.getTestingScope();
scope.field.Value = '13/11/2015 09:40:00';
ctrl = instance.compile<Controller>(scope, instance.compileString);
expect(ctrl.wrapper.timeValue).toBe('09:40');
})
it('Successful update with validate datetime', () => {
let ctrl: Controller, scope: any, valid: boolean;
scope = instance.getTestingScope();
scope.field.Value = '13/11/2015 09:11:00';
ctrl = instance.compile<Controller>(scope, instance.compileString);
ctrl.wrapper.timeValue = '19:50';
instance.$httpBackend.expectPOST(ctrl.updateUrl).respond((method, url, data: string, headers, params) => {
let parsedArgs = JSON.parse(data);
valid = parsedArgs.NewValue === '13/11/2015, 7:50:00 PM';
return [201, { Success: true }];
});
ctrl.update();
instance.$httpBackend.flush();
expect(valid).toBe(true);
});
it('Successful update with validate datetime that starts with a 0', () => {
let ctrl: Controller, scope: any, valid: boolean;
scope = instance.getTestingScope();
scope.field.Value = '13/11/2015 09:20:00';
ctrl = instance.compile<Controller>(scope, instance.compileString);
ctrl.wrapper.timeValue = '09:50';
instance.$httpBackend.expectPOST(ctrl.updateUrl).respond((method, url, data: string, headers, params) => {
let parsedArgs = JSON.parse(data);
valid = parsedArgs.NewValue === '13/11/2015, 9:50:00 AM';
return [201, { Success: true }];
});
ctrl.update();
instance.$httpBackend.flush();
expect(valid).toBe(true);
});
})
});

View File

@ -0,0 +1,52 @@
<form name="datetimeForm">
<div class="row row-center">
<div class="col-66">
<label class="item item-input">
<input type="date" ng-model="vm.wrapper.workingValue" placeholder="dd/mm/yyyy" autofocus />
</label>
</div>
<div class="col-33">
<label class="item item-input">
<input type="text"
ng-pattern="vm.wrapper.timeValueRegEx"
ng-model="vm.wrapper.timeValue"
placeholder="00:00" />
</label>
</div>
</div>
</form>
<!--<div class="row row-center">
<div class="col text-center text-center primary-color clear-action">Clear?</div>
</div>-->
<div class="row row-bottom">
<div class="col text-center">
<span class="text-center">{{vm.error}}</span>
<button class="action update-button"
style="width: 100%;"
ng-disabled="!datetimeForm.$pristine && (!vm.wrapper.timeValue || !datetimeForm.$valid)"
ng-hide="datetimeForm.$pristine"
ng-class="{pending: vm.status === 'P',
success: vm.status === 'S',
error: vm.status === 'E'}"
ng-click="vm.update(datetimeForm.$pristine);">
<span ng-if="vm.status === undefined && !datetimeForm.$pristine">Save</span>
<span ng-if="vm.status === 'P'">
<ion-spinner icon="spiral" class="primary-color vcenter"></ion-spinner>
</span>
<span ng-if="vm.status === 'S'">
<i class="icon ion-ios-checkmark-outline white"></i>
</span>
<span ng-if="vm.status === 'E'">
<i class="icon ion-android-cancel white cancel-button"></i>
</span>
</button>
<a class="animated fadeIn pull-right"
ng-click="vm.instance.close();"
ng-hide="vm.status === 'E'">
<i class="icon ion-android-cancel primary-color cancel-button"></i>
</a>
</div>
</div>

View File

@ -0,0 +1,119 @@
import {controller, directive} from '../../../infrastructure/Dectorators/Components';
import * as InformationService from '../../services/flightInformationService';
import Mod from '../../flight-detail.mod';
import * as BaseEditor from '../baseEditor';
//TESTONLY import * as moment from 'moment'
export class DateWrapper {
//actual value
private _value: Date;
//Original value: only used to check if the date was removed
public originalValue: string;
//working value: what angular is bound to
public workingValue: Date;
public timeValue: string;
constructor(value: string) {
if (value != null && value.slice(-1) === 'Z') {
this._value = moment(value).toDate();
this.workingValue = moment(value).toDate();
} else {
this._value = moment(value, 'DD/MM/YYYY hh:mm:ss').toDate();
this.workingValue = moment(value, 'DD/MM/YYYY hh:mm:ss').toDate();
}
this.originalValue = value;
if (value !== '') {
this.timeValue = `${this.addZero(this.workingValue.getHours()) }:${this.addZero(this.workingValue.getMinutes()) }`;
}
}
public getValue(): string {
if (!this.workingValue) {
return '';
}
this._value.setFullYear(this.workingValue.getFullYear());
this._value.setMonth(this.workingValue.getMonth());
this._value.setDate(this.workingValue.getDate());
let timeSeg = this.timeValue.split(':');
let hours = timeSeg[0];
this._value.setHours(parseInt(this.trimZero(hours), 10));
this._value.setMinutes(parseInt(timeSeg[1], 10));
return this._value.toISOString();
}
private addZero(i: any): string | number {
if (i < 10) {
return '0' + i.toString();
}
return i;
}
private trimZero(i: any): string {
if (i < 10) {
let returnStr = i.toString();
return returnStr.substr(1, 1);
}
return i.toString();
}
}
@controller(Mod, 'dateTimeEditorController', ['$http', '$rootScope', '$timeout', 'api'])
export class Controller extends BaseEditor.BaseEditorController {
public wrapper: DateWrapper;
constructor($http: ng.IHttpService, $rootScope: ng.IRootScopeService, $timeout: ng.ITimeoutService, api: any, Jquery: any) {
super($http, $rootScope, $timeout, api);
this.wrapper = new DateWrapper(this.field.Value);
}
public update(): void {
if (this.definition.Url !== '') {
this.error = '';
var val = this.extractVal();
if (val !== undefined) {
super.updateValue({
Id: this.model.Flight.Id,
NewValue: val,
Mapping: this.field.Mapping,
IsDelete: this.wrapper.originalValue && !val
});
}
} else {
this.field.Value = this.extractVal();
this.instance.close();
}
}
private extractVal() {
let val: string = undefined;
try {
val = this.wrapper.getValue();
} catch (error) {
this.error = 'Date/Time not in the correct format';
}
return val;
}
}
@directive(Mod, 'chromaDatetimeEditor')
export class Directive implements ng.IDirective {
controller = Controller.$componentName;
controllerAs = 'vm';
templateUrl = 'app/flight-detail/editors/datetime/datetime.tpl.html';
restrict = 'E';
replace = false;
bindToController = true;
scope: any = {
field: '=',
definition: '=',
instance: '=',
model: '='
};
}

View File

@ -0,0 +1,64 @@
@primary-color: #00398a;
@success-green: #689F38;
@error-red: #c62828;
.invalid {
color: @error-red !important;
}
.editor {
.clear-action {
margin: 5px;
font-size: 110%;
}
.cancel-button {
font-size: 250%;
}
.item-input {
&[disabled] {
background-color: #f8f8f8;
}
input {
padding-right: 0px;
}
}
.popup-head {
padding-bottom: 5px;
}
.popup-title {
color: #00398a;
font-family: 'Roboto-Thin';
}
button.action {
font-size: 140%;
i {
font-size: 200%;
}
&.pending {
background-color: transparent !important;
}
&.success {
background-color: @success-green !important;
border-color: @success-green !important;
}
&.error {
background-color: @error-red !important;
border-color: @error-red !important;
}
}
button.action[disabled]{
opacity:0.5;
}
}

View File

@ -0,0 +1,5 @@
<chroma:Freetext-Editor definition="editor"
field="field"
instance="instance"
model="model">
</chroma:Freetext-Editor>

View File

@ -0,0 +1,150 @@
import {ComponentTest} from '../../../infrastructure/ComponentHelper';
import DetailMod from '../../flight-detail.mod';
import {Mod as CoreMod} from '../../../core/core.mod';
import {Directive, Controller} from './freetext'
import * as InformationService from "../../services/flightInformationService";
class FreeTextEditorTest extends ComponentTest {
public compileString: string = `<chroma:Freetext-Editor definition="definition" field="field"instance="instance"model="model"> </chroma:Freetext-Editor>`;
constructor() {
super(Directive.$componentName, 'app/flight-detail/editors/freetext/freetext.tpl.html');
}
public getTestingScope(): any {
return {
field: {
Value: 'testValue',
Name: 'Crew',
Editor: 'freetext',
Group: 'Details',
Mapping: 'SCCREW'
},
definition: {
Url: 'testURL'
},
model: {
Flight: {
Id: '0001'
}
},
instance: {
close() {
}
}
};
}
public setupForRequest(scope: any, responseObj: any, beforeUpdate: any = undefined): Controller {
let ctrl = this.compile<Controller>(scope, this.compileString);
this.$httpBackend.expectPOST(ctrl.updateUrl).respond(201, responseObj);
if (beforeUpdate) {
beforeUpdate(ctrl);
}
ctrl.update();
this.$httpBackend.flush();
return ctrl;
}
}
describe('Freetext Editor:', () => {
let instance: FreeTextEditorTest,
detailMod: DetailMod,
coreMod: CoreMod;
beforeEach(() => {
detailMod = new DetailMod(angular);
coreMod = new CoreMod(angular);
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('chroma.configuration');
angular.mock.module(DetailMod.$componentName);
angular.mock.module(CoreMod.$componentName);
instance = new FreeTextEditorTest();
});
describe('Framework:', () => {
it('Compiles with required properties', () => {
let ctrl: Controller,
scope: any;
scope = instance.getTestingScope();
ctrl = instance.compile<Controller>(scope, instance.compileString);
expect(ctrl).toBeDefined();
expect(ctrl.field).toBeDefined();
expect(ctrl.definition).toBeDefined();
expect(ctrl.model).toBeDefined();
expect(ctrl.instance).toBeDefined();
});
it('Popup closes if previous action complete', () => {
let ctrl: Controller, scope: any;
scope = instance.getTestingScope();
ctrl = instance.setupForRequest(scope, { Success: false });
spyOn(ctrl.instance, 'close').and.callThrough();
ctrl.update();
expect(ctrl.instance.close).toHaveBeenCalled();
});
});
describe('Behaviour', () => {
it('Flight updated even broadcast on update regardless on status', () => {
let ctrl: Controller, scope: any,
flightUpdateEvent: string = 'chroma:flight-updated';
scope = instance.getTestingScope();
spyOn(instance.$rootScope, '$emit').and.callThrough();
instance.setupForRequest(scope, { Success: true });
expect(instance.$rootScope.$emit).toHaveBeenCalledWith(flightUpdateEvent);
});
it('Status becomes S if successful Update', () => {
let ctrl: Controller, scope: any;
scope = instance.getTestingScope();
ctrl = instance.setupForRequest(scope, { Success: true });
expect(ctrl.status).toBe('S');
});
it('Status becomes S if successful Delete', () => {
let ctrl: Controller, scope: any;
scope = instance.getTestingScope();
ctrl = instance.setupForRequest(scope, {}, (controller: Controller) => {
controller.value = '';
});
expect(ctrl.status).toBe('S');
});
it('Status becomes E if on failed update or delete', () => {
let ctrl: Controller, scope: any;
scope = instance.getTestingScope();
ctrl = instance.setupForRequest(scope, { Success: false });
expect(ctrl.status).toBe('E');
});
});
});

View File

@ -0,0 +1,38 @@
<form name="freetextForm">
<div class="row row-center">
<div class="col">
<label class="item item-input">
<input type="text" ng-model="vm.value" autofocus />
</label>
</div>
</div>
</form>
<div class="row row-bottom">
<div class="col text-center">
<span class="text-center">{{vm.error}}</span>
<button class="action update-button"
style="width: 100%;"
ng-hide="freetextForm.$pristine"
ng-class="{pending: vm.status === 'P',
success: vm.status === 'S',
error: vm.status === 'E'}"
ng-click="vm.update(freetextForm.$pristine);">
<span ng-if="vm.status === undefined && !freetextForm.$pristine">Save</span>
<span ng-if="vm.status === 'P'">
<ion-spinner icon="spiral" class="primary-color vcenter"></ion-spinner>
</span>
<span ng-if="vm.status === 'S'">
<i class="icon ion-ios-checkmark-outline white"></i>
</span>
<span ng-if="vm.status === 'E'">
<i class="icon ion-android-cancel white cancel-button"></i>
</span>
</button>
<a class="animated fadeIn pull-right"
ng-click="vm.instance.close();"
ng-hide="vm.status === 'E'">
<i class="icon ion-android-cancel primary-color cancel-button"></i>
</a>
</div>
</div>

View File

@ -0,0 +1,69 @@
import {controller, directive} from '../../../infrastructure/Dectorators/Components';
import * as InformationService from '../../services/flightInformationService';
import Mod from '../../flight-detail.mod';
import * as BaseEditor from '../baseEditor';
interface IFlightUpdateRequest {
Id: string;
NewValue: string;
Mapping: string;
IsDelete: Boolean;
}
@controller(Mod, 'freeTextEditorController', ['$http', '$rootScope', '$timeout', 'api'])
export class Controller extends BaseEditor.BaseEditorController {
public value: string;
public originalValue: string;
constructor($http: ng.IHttpService, $rootScope: ng.IRootScopeService, $timeout: ng.ITimeoutService, api: any, jquery: any) {
super($http, $rootScope, $timeout, api);
this.value = this.field.Value;
this.originalValue = this.value;
}
public update(): void {
if (this.valueNotChanged()) {
this.instance.close();
return;
}
if (this.definition.Url !== '') {
this.updateValue(this.createRequestParams());
} else {
this.field.Value = this.value;
this.instance.close();
}
}
private valueNotChanged() {
if (this.value === this.originalValue) {
return true;
}
return false;
}
private createRequestParams(): IFlightUpdateRequest {
return {
Id: this.model.Flight.Id,
NewValue: this.value,
Mapping: this.field.Mapping,
IsDelete: this.originalValue && this.value === ''
};
}
}
@directive(Mod, 'chromaFreetextEditor')
export class Directive implements ng.IDirective {
controller: string = Controller.$componentName;
controllerAs: string = 'vm';
templateUrl: string = 'app/flight-detail/editors/freetext/freetext.tpl.html';
restrict: string = 'E';
replace: boolean = false;
bindToController: boolean = true;
scope: any = {
field: '=',
definition: '=',
instance: '=',
model: '='
};
}

View File

@ -0,0 +1,6 @@
<chroma:Lookup-Editor definition="editor"
field="field"
instance="instance"
model="model"
lookupmodel="lookupmodel">
</chroma:Lookup-Editor>

View File

@ -0,0 +1,20 @@
<div style="height:55%; width:100%">
<div class="row row-center">
<input type="text" ng-model="vm.searchInput" ng-change="vm.onSearchTextChanged()" />
</div>
<ion-list>
<ion-item ng-repeat="result in vm.field.LookupModel.results">
<a class="item" on-tap="vm.selectItem(result)">
<div class="row">
<div class="text-left primary-color shunt-down-text" style="white-space: normal; word-wrap: break-word;">
<span class="descriptor">
{{result[vm.field.LookupModel.resultField]}}
</span>
</div>
</div>
</a>
</ion-item>
</ion-list>
</div>

View File

@ -0,0 +1,49 @@
import {controller, directive} from '../../../infrastructure/Dectorators/Components';
import Mod from '../../flight-detail.mod';
import {LookupModel, LookupEditorField} from '../../services/flightInformationService';
import * as BaseEditor from '../baseEditor';
@controller(Mod, 'lookupEditorController', ['$http', '$rootScope', '$timeout', 'api'])
export class Controller extends BaseEditor.BaseEditorController {
constructor($http: ng.IHttpService, $rootScope: ng.IRootScopeService, $timeout: ng.ITimeoutService, api: any) {
super($http, $rootScope, $timeout, api);
}
public lookupmodel: LookupModel;
public searchInput: string;
public onSearchTextChanged() {
var field: LookupEditorField;
field = this.field as LookupEditorField;
field.LookupModel.searchString = this.searchInput;
field.LookupModel.search();
}
public selectItem(item: any) {
var field: LookupEditorField;
field = this.field as LookupEditorField;
field.LookupModel.onSelected(item);
this.instance.close();
}
}
@directive(Mod, 'chromaLookupEditor')
export class Directive implements ng.IDirective {
controller = Controller.$componentName;
controllerAs = 'vm';
templateUrl:string = 'app/flight-detail/editors/lookup/lookup.tpl.html';
restrict:string = 'E';
replace: boolean = false;
bindToController:boolean = true;
scope: any = {
field: '=',
definition: '=',
instance: '=',
model: '=',
lookupmodel: '='
};
}

View File

@ -0,0 +1,13 @@
@primary-color: #00398a;
.editor-readonly{
.popup-head{
padding-bottom: 5px;
}
.popup-title{
color: #00398a;
}
button.action{
font-size: 100%
}
}

View File

@ -0,0 +1,15 @@
<div class="row row-center">
<div class="col">
<label class="item item-input">
<input type="text" value="{{field.Value}}" />
</label>
</div>
</div>
<div class="row row-bottom">
<div class="col">
<button class="action" style="width:100%;" ng-click="model.close();">
Save
</button>
</div>
</div>

View File

@ -0,0 +1,20 @@
import {inject} from '../infrastructure/Dectorators/Components';
@inject(['$stateProvider'])
export class Routes {
constructor($stateProvider: ng.ui.IStateProvider) {
$stateProvider.state('chroma.flight-detail', {
url: '/flight-detail/:id',
params: {
flight: undefined,
id: undefined,
requestId: undefined
},
views: {
'content': {
template: '<chroma:Flight-Detail></chroma:Flight-Detail>'
}
}
});
}
}

View File

@ -0,0 +1,57 @@
import {module} from '../infrastructure/Dectorators/Components';
import {Routes} from './flight-detail.config';
import {Controller as FltDetailCtrl, Directive as FltDetailDir} from './detail/flight-detail';
import {Controller as TransactionLstCtrl, Directive as TransactionLstDir} from './transactions/transaction-list';
import {Controller as TransactionDetCtrl, Directive as TransactionDetDir} from './transactions/transaction-detail';
import {Controller as PrmLstCtrl, Directive as PrmLstDir} from './prm/prm-list';
import {Controller as FltGroupCtrl, Directive as FltGroupDir} from './group/flight-group';
import {Controller as FreeTextCtrl, Directive as FreeTextDir} from './editors/freetext/freetext';
import {Controller as DateTimeCtrl, Directive as DateTimeDir} from './editors/datetime/datetime';
import {Controller as LookupCtrl, Directive as LookupCtrlDir} from './editors/lookup/lookup';
import {FlightInformationService} from './services/flightInformationService';
import {TransactionService} from './services/transactionService';
import {PrmService} from './services/prmService';
@module('flight-detail')
export default class Mod {
constructor(angular: ng.IAngularStatic) {
angular.module(Mod.$componentName, [])
.factory(FlightInformationService.$componentName, FlightInformationService.$factory)
.controller(FltDetailCtrl.$componentName, FltDetailCtrl)
.directive(FltDetailDir.$componentName, FltDetailDir.$factory)
.controller(TransactionLstCtrl.$componentName, TransactionLstCtrl)
.directive(TransactionLstDir.$componentName, TransactionLstDir.$factory)
.factory(TransactionService.$componentName, TransactionService.$factory)
.controller(PrmLstCtrl.$componentName, PrmLstCtrl)
.directive(PrmLstDir.$componentName, PrmLstDir.$factory)
.factory(PrmService.$componentName, PrmService.$factory)
.controller(TransactionDetCtrl.$componentName, TransactionDetCtrl)
.directive(TransactionDetDir.$componentName, TransactionDetDir.$factory)
.controller(FltGroupCtrl.$componentName, FltGroupCtrl)
.directive(FltGroupDir.$componentName, FltGroupDir.$factory)
.controller(FreeTextCtrl.$componentName, FreeTextCtrl)
.directive(FreeTextDir.$componentName, FreeTextDir.$factory)
.controller(DateTimeCtrl.$componentName, DateTimeCtrl)
.directive(DateTimeDir.$componentName, DateTimeDir.$factory)
.controller(LookupCtrl.$componentName, LookupCtrl)
.directive(LookupCtrlDir.$componentName, LookupCtrlDir.$factory)
.filter('chromaDateFilter', $filter => (filterText, date) => {
if (!filterText || filterText === '/Date(-62135596800000)/') {
return '';
}
return date ? $filter('date')(parseInt(filterText.substr(6), 10), '[dd] HH:mm') : $filter('date')(parseInt(filterText.substr(6), 10), 'HH:mm');
})
.config(Routes);
}
}

View File

@ -0,0 +1,241 @@
import {ComponentTest} from '../../infrastructure/ComponentHelper';
import {Directive as GroupDir, Controller as GroupCtrl, IGroupParams} from './flight-group';
import DetailMod from '../flight-detail.mod';
import {Mod as CoreMod} from '../../core/core.mod';
import {IFlightInformationModel, IFlightInformation} from '../services/flightInformationService';
import {IFlightDetail} from '../../core/service/flightService';
import * as InformationService from "../services/flightInformationService";
import * as Flightdetail from "../detail/flight-detail";
class FlightGroupTest extends ComponentTest {
constructor() {
super(GroupDir.$componentName, 'app/flight-detail/group/flight-group.tpl.html');
}
}
class TestInstance {
public compileString: string = "<chroma:Flight-Detail-Group model='model'></chroma:Flight-Detail-Group>";
public test: FlightGroupTest;
public testModel: IFlightInformationModel;
public api: any;
public $httpBackend: ng.IHttpBackendService;
public $state: ng.ui.IStateService;
public $stateParams: ng.ui.IStateParamsService;
public $rootScope: any;
public $ionicPopup: any;
constructor() {
angular.mock.inject(($httpBackend, $state, $stateParams, $ionicPopup, $rootScope, api) => {
this.$httpBackend = $httpBackend;
this.$state = $state;
this.$stateParams = $stateParams;
this.$ionicPopup = $ionicPopup;
this.$rootScope = $rootScope;
this.api = api;
});
}
public setup(): void {
this.testModel = {
IsOutsideOfWindow: false,
Flight: {
Id: undefined,
Location: undefined,
Operator: undefined,
Number: undefined,
AircraftType: undefined,
Registration: undefined,
Stand: undefined,
Terminal: undefined,
Actual: undefined,
Scheduled: undefined,
Estimated: undefined,
FlightConcat: undefined,
Type: 'D'
},
Groups: undefined,
Fields: [
{
"Value": '',
"Name": 'Mail Weight',
"Editor": 'freetext',
"Mapping": 'MAIL_WEIGHT',
"Group": 'Deadload',
"Restrict": "A"
},
{
"Value": '',
"Name": 'PAX Weight',
"Editor": 'freetext',
"Mapping": 'PAX_WEIGHT',
"Group": 'General'
}
],
Editors: [
{
Name: 'freetext',
Type: 'freetext',
Url: 'test'
}
]
};
this.getStateParams();
}
public getStateParams(): void {
let params: any = this.$stateParams;
params.group = {
"Name": 'Deadload',
"Display": 'Deadload Figures',
"Icon": 'ion-navicon'
};
}
}
describe('Flight Group:', () => {
let instance: TestInstance;
beforeEach(() => {
new DetailMod(angular);
new CoreMod(angular);
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('chroma.configuration');
angular.mock.module(DetailMod.$componentName);
angular.mock.module(CoreMod.$componentName);
instance = new TestInstance();
instance.test = new FlightGroupTest();
});
describe('Framework:', () => {
it('View Refreshed on pull down', () => {
let ctrl: GroupCtrl,
flightUpdateEvent: string = 'chroma:flight-updated';
spyOn(instance.$rootScope, '$emit').and.callThrough();
instance.setup();
ctrl = instance.test.compile<GroupCtrl>({ model: instance.testModel }, instance.compileString);
ctrl.update();
expect(instance.$rootScope.$emit).toHaveBeenCalledWith(flightUpdateEvent);
});
it('Filter correct removes fields with restrictions', () => {
let ctrl: GroupCtrl,
valid: Boolean;
spyOn(instance.$rootScope, '$emit').and.callThrough();
instance.setup();
ctrl = instance.test.compile<GroupCtrl>({ model: instance.testModel }, instance.compileString);
valid = ctrl.fieldFilter(instance.testModel.Fields[1]);
expect(valid).toBe(false);
});
it('Filter returns fields for correct group', () => {
let ctrl: GroupCtrl,
valid: Boolean;
instance.setup();
ctrl = instance.test.compile<GroupCtrl>({ model: instance.testModel }, instance.compileString);
valid = ctrl.fieldFilter(instance.testModel.Fields[0]);
expect(valid).toBe(false);
});
});
describe('Scenario: Updating Mail Weight', () => {
//Given that I have chosen to enter a dead load for flight SAS6876
//And the limit is 99999kg
//When I enter a mail weight of 34.67kg for flight SAS6876
//Then flight SAS6876 is updated with a mail weight of 34.67kg
it('Directive loaded and required Params present', function() {
let ctrl: GroupCtrl;
instance.setup();
ctrl = instance.test.compile<GroupCtrl>({ model: instance.testModel }, instance.compileString);
expect(ctrl.group).toBeDefined();
expect(ctrl.model).toBeDefined();
});
it('Readonly Editor has no interaction', () => {
let ctrl: GroupCtrl,
testField: IFlightInformation;
testField = {
"Value": '',
"Name": '',
"Editor": 'Readonly',
"Group": '',
"Mapping": ''
};
spyOn(instance.$ionicPopup, 'show').and.returnValue(false);
instance.setup();
ctrl = instance.test.compile<GroupCtrl>({ model: instance.testModel }, instance.compileString);
ctrl.editorFor(testField);
expect(instance.$ionicPopup.show).not.toHaveBeenCalled();
});
it('Freetext Editor shown', () => {
let ctrl: GroupCtrl,
testField: IFlightInformation,
argumentsAreCorrect: boolean = false;
testField = {
"Value": '',
"Name": 'PAX Weight',
"Editor": 'freetext',
"Group": 'Deadload',
"Mapping": 'PAX_WEIGHT'
};
spyOn(instance.$ionicPopup, 'show').and.callFake((popup) => {
let comparableTemplate = `app/flight-detail/editors/${testField.Editor}/${testField.Editor}-holder.tpl.html`;
let compareableClass = `editor editor-${testField.Editor}`;
let comparableTitle = `Update ${testField.Name}`;
if (popup.templateUrl === comparableTemplate &&
popup.cssClass === compareableClass &&
popup.title === comparableTitle) {
argumentsAreCorrect = true;
}
});
instance.setup();
ctrl = instance.test.compile<GroupCtrl>({ model: instance.testModel }, instance.compileString);
ctrl.editorFor(testField);
expect(argumentsAreCorrect).toBe(true);
});
});
});

View File

@ -0,0 +1,17 @@
<ion-view>
<ion-content class="padding" style="padding-top: 0px;" scroll="true" has-bouncing="true">
<ion-refresher class="primary-color" pulling-text="Refresh" on-refresh="vm.update();" spinner="none">
</ion-refresher>
<div class="group-list list list-inset" style="padding: 14x;">
<label ng-repeat="field in vm.model.Fields | filter: vm.fieldFilter" class="item item-input editor-field">
<span class="input-label font-thin">{{field.Name}}</span>
<input id="{{field.Name}}" type="text" class="primary-color" placeholder="{{field.Editor === 'readonly' ? 'Readonly' : 'Update'}}"
ng-class="{readonly: field.Editor === 'readonly'}"
ng-model="field.Value"
on-tap="vm.editorFor(field)"
disabled="disabled"/>
</label>
</div>
</ion-content>
</ion-view>

View File

@ -0,0 +1,81 @@
import {controller, directive} from '../../infrastructure/Dectorators/Components';
import {Controller as FlightDetailController} from '../detail/flight-detail';
import Mod from '../flight-detail.mod';
import * as InformationService from '../services/flightInformationService';
export interface IGroupScope {
group: string;
vm: IController;
}
export interface IGroupParams {
group: InformationService.IFlightInformationGroup;
}
export interface IController extends IGroupScope {
model: InformationService.IFlightInformationModel;
}
@controller(Mod, 'flightGroupController', ['$ionicPopup', '$rootScope', '$stateParams'])
export class Controller {
public group: InformationService.IFlightInformationGroup;
public model: InformationService.IFlightInformationModel;
public fieldFilter: (field: InformationService.IFlightInformation) => Boolean;
constructor(private $ionicPopup: ionic.popup.IonicPopupService,
private $rootScope: ng.IRootScopeService, private $stateParams: IGroupParams) {
this.group = $stateParams.group;
this.fieldFilter = (field: InformationService.IFlightInformation) => {
if (this.$stateParams && this.$stateParams.group) {
if (!field.Restrict || field.Restrict === '') {
return field.Group.toLowerCase() === this.$stateParams.group.Name.toLowerCase();
} else {
return field.Group === this.group.Name
&& this.model.Flight.Type === field.Restrict;
}
}
};
$rootScope.$emit('flightGroupChanged', this.group);
}
public editorFor(field: InformationService.IFlightInformation) {
if (field.Editor.toLowerCase() === 'readonly') { return; }
let editor = field.Editor.toLowerCase();
let modalScope: any = this.$rootScope.$new();
let editorDefinition: InformationService.IFlightEditor;
this.model.Editors.forEach(e => {
if (e.Name.toLowerCase() === editor) {
editorDefinition = e;
}
});
modalScope.model = this.model;
modalScope.field = field;
modalScope.editor = editorDefinition;
modalScope.instance = this.$ionicPopup.show({
templateUrl: `app/flight-detail/editors/${editor}/${editor}-holder.tpl.html`,
cssClass: `editor editor-${editor}`,
title: `Update ${field.Name}`,
scope: modalScope
});
}
public update(): void {
this.$rootScope.$emit('chroma:flight-updated');
}
}
@directive(Mod, 'chromaFlightDetailGroup')
export class Directive implements ng.IDirective {
controller: string = Controller.$componentName;
controllerAs: string = 'vm';
templateUrl: string = 'app/flight-detail/group/flight-group.tpl.html';
restrict: string = 'E';
replace: boolean = false;
bindToController: boolean = true;
scope: any = {
model: '='
};
}

View File

View File

View File

@ -0,0 +1,7 @@
<ion-view view-title="PRM List">
<ion-content class="padding" scroll="true" has-bounce="true">
<div class="row">
<span class="input-label font-thin">Hello World!</span>
</div>
</ion-content>
</ion-view>

View File

@ -0,0 +1,53 @@
import {controller, directive} from '../../infrastructure/Dectorators/Components';
import {Mod} from './prm.mod';
import {IPrmService, IPrmDetail, IPrmListRequest} from '../services/prmService';
import * as InformationService from '../services/flightInformationService';
import {IFlightDetail} from '../../core/service/flightService';
export interface PrmListParams extends ng.ui.IStateParamsService {
flightId: string;
}
@controller(Mod, 'prmListController', ['prmService', '$stateParams', '$state', '$rootScope', 'chromaState'])
export class Controller {
public static NO_PRM = 'This flight has no passengers with reduced movement';
public prms: Array<IPrmDetail>;
public message: string;
public request: IPrmListRequest;
public flightId: string;
public model: IFlightDetail;
constructor(private prmService: IPrmService,
private $stateParams: PrmListParams,
private $state: ng.ui.IStateService,
private $rootScope: ng.IScope) {
this.load();
}
private load() : void {
this.prmService.getPrmList(this.model.Id, this.model.PhysFlightId)
.then((r) => this.onPrmLoad(r));
}
private onPrmLoad(request: any): void {
this.request = request;
this.prms = request;
if (!this.prms || this.prms.length === 0) {
this.message = Controller.NO_PRM;
}
}
}
@directive(Mod, 'chromaPrmList', ['$stateParams'])
export class Directive implements ng.IDirective {
controller: string = Controller.$componentName;
controllerAs: string = 'vm';
templateUrl: string = 'app/flight-detail/prm/prm-list.tpl.html';
restrict: string = 'E';
replace: boolean = false;
bindToController: boolean = true;
scope: any = {
model: '='
};
}

View File

@ -0,0 +1,19 @@
import {inject} from '../../infrastructure/Dectorators/Components';
@inject(['$stateProvider'])
export class Routes {
constructor($stateProvider: ng.ui.IStateProvider) {
$stateProvider
.state('chroma.flight-detail.prm-list', {
url: '/prm-list',
params: {
prm: undefined
},
views: {
'detail':{
template: '<chroma:Prm-List></chroma:Prm-List>'
}
}
});
}
}

View File

@ -0,0 +1,16 @@
import Dectorators = require('../../infrastructure/Dectorators/Components');
import PrmListComp = require('./prm-list');
import {PrmService} from '../services/prmService';
import Config = require ('./prm.config');
@Dectorators.module('prm-list')
export class Mod {
constructor(angular: ng.IAngularStatic) {
angular.module(Mod.$componentName, [])
.factory(PrmService.$componentName, PrmService.$factory)
.controller(PrmListComp.Controller.$componentName, PrmListComp.Controller)
.directive(PrmListComp.Directive.$componentName, PrmListComp.Directive.$factory)
.config(Config.Routes);
}
}

View File

@ -0,0 +1,82 @@
import {FlightInformationService} from './flightInformationService';
import {IFlightDetail} from '../../core/service/flightService';
describe('Framework: Flight info service', () => {
let service: FlightInformationService,
sourceUrl: string = 'test';
beforeEach(() => {
service = new FlightInformationService(null, null, null, {
imageSource: sourceUrl
});
});
it('Get Operater URL: Should concat URL based on API and Operator Code', () => {
let operator = "SteAir";
expect(service.getOperatorUrl(operator)).toBe(`${sourceUrl}?code=${operator}`);
});
it('Get Time Display: Should return nothing for null flight', () => {
expect(service.getTimeDisplayEnum(undefined)).toBe('');
});
it('Get Time Display: Should return A for flight with Actual', () => {
let flight: IFlightDetail = {
Id: undefined,
Operator: undefined,
Number: undefined,
Stand: undefined,
Scheduled: undefined,
Terminal: undefined,
Type: undefined,
Estimated: undefined,
Registration: undefined,
AircraftType: undefined,
Location: undefined,
Actual: '0000',
FlightConcat: undefined
}
expect(service.getTimeDisplayEnum(flight)).toBe('A');
});
it('Get Time Display: Should return E for flight with Estimated', () => {
let flight: IFlightDetail = {
Id: undefined,
Operator: undefined,
Number: undefined,
Stand: undefined,
Scheduled: undefined,
Terminal: undefined,
Type: undefined,
Estimated: '0000',
Registration: undefined,
AircraftType: undefined,
Location: undefined,
Actual: undefined,
FlightConcat: undefined
};
expect(service.getTimeDisplayEnum(flight)).toBe('E');
});
it('Get Time Display: Should return S for flight with Schedule', () => {
let flight: IFlightDetail = {
Id: undefined,
Operator: undefined,
Number: undefined,
Stand: undefined,
Scheduled: '0000',
Terminal: undefined,
Type: undefined,
Estimated: undefined,
Registration: undefined,
AircraftType: undefined,
Location: undefined,
Actual: undefined,
FlightConcat: undefined
};
expect(service.getTimeDisplayEnum(flight)).toBe('S');
});
});

View File

@ -0,0 +1,154 @@
import {factory} from '../../infrastructure/Dectorators/Components';
import {IFlightDetail} from '../../core/service/flightService';
import {Mod} from '../../core/core.mod';
export interface IFlightInformationModel {
Fields: Array<IFlightInformation>;
Groups: Array<IFlightInformationGroup>;
Editors: Array<IFlightEditor>;
Flight: IFlightDetail;
IsOutsideOfWindow: boolean;
}
export class LookupModel implements ILookupModel {
public searchString: string;
public lookupList: Array<any>;
public results: Array<any>;
public viewScope: any;
lookupField: string;
resultField: string;
public search() {
//
};
public onSelected(item: any) {
//
};
}
export interface ILookupModel {
lookupField: string;
resultField: string;
viewScope: any;
search() : any;
onSelected(item: any);
}
export class EditorField implements IFlightInformation {
constructor(public Name: string,
public Value: string,
public Editor: string,
public Group: string,
public Mapping: string,
public Restrict?: string,
public IsRequired?: boolean,
public Invisible?: boolean,
public Invalid?: boolean) {
}
public ChangeEventHandler() {
//
}
}
export class LookupEditorField implements EditorField {
constructor(public Name: string,
public Value: string,
public Editor: string,
public Group: string,
public Mapping: string,
public LookupModel: LookupModel,
public Restrict?: string,
public IsRequired?: boolean,
public Invisible?: boolean,
public Invalid?: boolean) {
}
public ChangeEventHandler() {
//
}
}
export interface IFlightInformation {
Value: string;
Name: string;
Editor: string;
Group: string;
Mapping: string;
Restrict?:string;
}
export interface IFlightInformationGroup {
Name: string;
Display: string;
Icon: string;
}
export interface IFlightEditor {
Name: string;
Type: string;
Url: string;
}
export interface IUserAccessModel {
ProfileCode: string;
FunctionDefinition: string;
Enabled: boolean;
Update: boolean;
Add: boolean;
Delete: boolean;
View: boolean;
FuncEnabled: boolean;
FuncPackage: boolean;
FuncProcedure: boolean;
FuncParamaters: boolean;
}
export interface IFlightInformationService {
getFlight(flight: IFlightDetail, requestId: string): ng.IPromise<IFlightInformationModel>;
getOperatorUrl(operator: string): string;
getTimeDisplayEnum(flight: IFlightDetail): string;
getTransactionAccess(): ng.IPromise<IUserAccessModel>;
}
@factory(Mod, 'flightInformationService', ['$http', '$q', '$rootScope', 'api'])
export class FlightInformationService implements IFlightInformationService {
constructor(private $http: ng.IHttpService,
private $q: ng.IQService,
private $rootScope: ng.IScope,
private api: any) {
}
public getFlight(flight: IFlightDetail, requestId: string): ng.IPromise<IFlightInformationModel> {
let def: ng.IDeferred<IFlightInformationModel> = this.$q.defer();
this.$http.get(`${this.api.detail}?flightId=${flight.Id}&requestId=${requestId}`).success((data, status) => {
def.resolve(<IFlightInformationModel>data);
});
return def.promise;
}
getOperatorUrl(operator: string): string {
return `${this.api.imageSource}?code=${operator}`;
}
public getTransactionAccess(): ng.IPromise<IUserAccessModel> {
let def: ng.IDeferred<IUserAccessModel> = this.$q.defer();
this.$http.get(this.api.getTransactionsAccess)
.success((data) => {
def.resolve(<IUserAccessModel>data);
});
return def.promise;
}
getTimeDisplayEnum(flight: IFlightDetail): string {
if (!flight) {
return '';
}
if (flight.Actual && flight.Actual !== '' && flight.Actual !== '/Date(-62135596800000)/') {
return 'A';
} else if (flight.Estimated && flight.Estimated !== '' && flight.Estimated !== '/Date(-62135596800000)/') {
return 'E';
}
return 'S';
}
}

View File

@ -0,0 +1,35 @@
import Decorators = require('../../infrastructure/Dectorators/Components');
import Core = require('../../core/core.mod');
export interface IPrmListRequest {
Prms: Array<IPrmDetail>;
}
export interface IPrmDetail {
Passenger: string;
Remark: string;
Comment: string;
Status: string;
}
export interface IPrmService {
getPrmList(flightId: string, physFlight: string) : ng.IPromise<IPrmListRequest>;
}
@Decorators.factory(Core.Mod, 'prmService', ['$http', '$q', '$rootScope', 'api'])
export class PrmService implements IPrmService {
constructor(private $http: ng.IHttpService,
private $q: ng.IQService,
private $rootScope: ng.IScope,
private api: any) {
}
getPrmList(flightId: string, physFlight: string) : ng.IPromise<IPrmListRequest> {
let def: ng.IDeferred<IPrmListRequest> = this.$q.defer();
this.$http.get(`${this.api.prmList}?flightId=${flightId}&physFlight=${physFlight}`).success((data: any, status) => {
def.resolve(data);
});
return def.promise;
}
}

View File

@ -0,0 +1,141 @@
import Decorators = require('../../infrastructure/Dectorators/Components');
import Core = require('../../core/core.mod');
import {IUpdateResonse} from '../editors/baseEditor';
export interface ITransactionListRequest {
Transactions: Array<ITransactionDetail>;
}
export interface ITransactionCodesRequest {
TransactionCodes: Array<TransactionCode>;
}
export interface IUpdateResonse {
Success: boolean;
Error: string;
}
export interface ITransactionConfigRequest {
TransactionConfig: Array<ITransactionConfig>;
}
export interface ITransactionDetail {
PublflightId: string;
PhysflightId: string;
Id: string;
Code:string;
Name:string;
Quantity: number;
Duration: string;
PONumber: string;
StartTime: string;
EndTime:string;
Confirmed: boolean;
Cancelled: boolean;
CodeType: number;
Timestamp: number;
}
export interface ITransactionConfig {
ColumnName: string;
Index: number;
HeaderText: string;
Justify: string;
Visible: boolean;
Width: number;
Description: string;
Editable: boolean;
Highlight: boolean;
Length: number;
}
export class TransactionCode {
Name: string;
Code: string;
CodeType: number;
}
export interface ITransactionService {
getTransactions(flightId: string, physFlight: string): ng.IPromise<ITransactionListRequest>;
confirmTransaction(transaction: ITransactionDetail);
cancelTransaction(transaction: ITransactionDetail);
createTransaction(transaction: ITransactionDetail);
getTransactionCodes() : ng.IPromise<ITransactionCodesRequest>;
getTransactionConfig(flightId: string, physFlight: string) : ng.IPromise<ITransactionConfigRequest>;
}
@Decorators.factory(Core.Mod, 'transactionService', ['$http', '$q', '$rootScope', 'api'])
export class TransactionService implements ITransactionService {
constructor(private $http: ng.IHttpService,
private $q: ng.IQService,
private $rootScope: ng.IScope,
private api: any) {
}
private status: string;
private error: string;
getTransactions(flightId: string, physFlight:string) : ng.IPromise<ITransactionListRequest> {
let def: ng.IDeferred<ITransactionListRequest> = this.$q.defer();
this.$http.get(`${this.api.transactions}?flightId=${flightId}&physFlight=${physFlight}`).success((data: any, status) => {
def.resolve(data);
});
return def.promise;
}
getTransactionConfig(flightId: string, physFlight: string) : ng.IPromise<ITransactionConfigRequest> {
let def: ng.IDeferred<ITransactionConfigRequest> = this.$q.defer();
this.$http.get(`${this.api.getTransactionConfig}?flightId=${flightId}&physFlight=${physFlight}`).success((data: any, status) => {
def.resolve(data);
});
return def.promise;
}
confirmTransaction(transaction: ITransactionDetail) : ng.IPromise<any> {
let def: ng.IDeferred<any> = this.$q.defer();
this.$http.post(this.api.confirmTransaction, transaction)
.success((response: IUpdateResonse) => {
this.handleUpdateResult(response);
def.resolve(response);
});
return def.promise;
}
cancelTransaction(transaction: ITransactionDetail) : ng.IPromise<any> {
let def: ng.IDeferred<any> = this.$q.defer();
this.$http.post(this.api.cancelTransaction, transaction)
.success((response: IUpdateResonse) => {
this.handleUpdateResult(response);
def.resolve(response);
});
return def.promise;
}
createTransaction(transaction: ITransactionDetail) : ng.IPromise<any> {
let def: ng.IDeferred<any> = this.$q.defer();
this.$http.post(this.api.createTransaction, transaction)
.success((response: IUpdateResonse) => {
def.resolve(response);
});
return def.promise;
}
getTransactionCodes() : ng.IPromise<ITransactionCodesRequest> {
let def: ng.IDeferred<ITransactionCodesRequest> = this.$q.defer();
this.$http.get(this.api.getTransactionCodes)
.success((data: any) => {
def.resolve(data);
});
return def.promise;
}
private handleUpdateResult(response: IUpdateResonse): void {
if ((response && response.Success)) {
this.status = 'S';
} else {
this.status = 'E';
this.error = response.Error;
}
}
}

View File

@ -0,0 +1,83 @@
<ion-view view-title="Transaction">
<ion-content class="padding" style="padding-top: 0px;" scroll="true" has-bouncing="true">
<ion-refresher class="primary-color" pulling-text="Refresh" on-refresh="vm.update();" spinner="none">
</ion-refresher>
<form name="transactionsForm" ng-submit="" >
<div class="group-list list list-inset" style="padding: 14x;">
<label ng-repeat="field in vm.fields | filter: vm.fieldFilter" class="item item-input editor-field" ng-hide="field.Invisible === true">
<span class="input-label font-thin" ng-class="{invalid: field.Invalid}">{{field.Name}}
</span>
<input class="text-right" type="text" placeholder="{{field.Editor === 'readonly' ? 'Readonly' : 'Update'}}"
ng-class="{'primary-color': field.Editor !== 'readonly', readonly: field.Editor === 'readonly'}"
ng-model="field.Value"
on-tap="vm.editorFor(field)"
disabled="field.Editor === 'readonly'"
ng-required="field.Editor.IsRequired"/>
</label>
</div>
<div ng-if="vm.IsNewAdd === false">
<button class="action update-button"
style="width: 49%;"
ng-if="vm.$stateParams.tranAccess.Update"
ng-class="{pending: vm.status === 'P',
success: vm.status === 'S',
error: vm.status === 'E'}"
ng-click="vm.confirmTransaction();"
ng-hide="vm.model.Confirmed || vm.model.Cancelled">
<span ng-if="vm.status === undefined">Confirm</span>
<span ng-if="vm.status === 'P'">
<ion-spinner icon="spiral" class="primary-color vcenter"></ion-spinner>
</span>
<span ng-if="vm.status === 'S'">
<i class="icon ion-ios-checkmark-outline white"></i>
</span>
<span ng-if="vm.status === 'E'">
<i class="icon ion-ios-close-empty white"></i>
</span>
</button>
<button class="action update-button"
style="width: 49%;"
ng-class="{pending: vm.status === 'P',
success: vm.status === 'S',
error: vm.status === 'E'}"
ng-click="vm.cancelTransaction();"
ng-if="vm.$stateParams.tranAccess.Update"
ng-hide="vm.model.Cancelled">
<span ng-if="vm.status === undefined">Cancel</span>
<span ng-if="vm.status === 'P'">
<ion-spinner icon="spiral" class="primary-color vcenter"></ion-spinner>
</span>
<span ng-if="vm.status === 'S'">
<i class="icon ion-ios-checkmark-outline white"></i>
</span>
<span ng-if="vm.status === 'E'">
<i class="icon ion-ios-close-empty white"></i>
</span>
</button>
</div>
<div ng-if="vm.IsNewAdd === true">
<button type="submit" class="action update-button"
style="width: 100%;"
ng-class="{pending: vm.status === 'P',
success: vm.status === 'S',
error: vm.status === 'E'}"
ng-click="vm.createTransaction();">
<span ng-if="vm.status === undefined">Create</span>
<span ng-if="vm.status === 'P'">
<ion-spinner icon="spiral" class="primary-color vcenter"></ion-spinner>
</span>
<span ng-if="vm.status === 'S'">
<i class="icon ion-ios-checkmark-outline white"></i>
</span>
<span ng-if="vm.status === 'E'">
<i class="icon ion-ios-close-empty white"></i>
</span>
</button>
</div>
</form>
</ion-content>
</ion-view>

View File

@ -0,0 +1,413 @@
import {controller, directive} from '../../infrastructure/Dectorators/Components';
import {ITransactionDetail} from '../services/transactionService';
import {Mod} from './transaction.mod';
import {IFlightInformation} from '../services/flightInformationService';
import {IFlightDetail} from '../../core/service/flightService';
import {ITransactionService, TransactionCode, ITransactionConfig} from '../services/transactionService';
import * as InformationService from '../services/flightInformationService';
import {LookupModel, EditorField, LookupEditorField} from '../services/flightInformationService';
export interface TransactionDetailParams extends ng.ui.IStateParamsService {
transaction: ITransactionDetail;
flight: IFlightDetail;
tranAccess: InformationService.IUserAccessModel;
columnConfig: Array<ITransactionConfig>;
}
export class TransactionDetail implements ITransactionDetail {
constructor(public PublflightId: string,
public Id: string,
public Code:string,
public Name:string,
public Quantity: number,
public Duration: string,
public PONumber: string,
public StartTime: string,
public EndTime: string,
public PhysflightId: string,
public Confirmed: boolean,
public Cancelled: boolean,
public CodeType: number,
public Timestamp: number) {
}
}
enum TransactionType {
QuantityOnly = 1,
TimesOnly,
TimeDurationAndQuantity
}
export class Editor implements InformationService.IFlightEditor {
constructor(public Name: string,
public Type: string,
public Url:string) {
}
}
@controller(Mod, 'transactionDetailController', ['$state', '$scope', '$stateParams', '$ionicPopup', '$rootScope', 'transactionService'])
export class Controller {
public model: ITransactionDetail;
public fields: Array<IFlightInformation>;
public editors: Array<InformationService.IFlightEditor>;
public columnConfig: Array<ITransactionConfig>;
public IsNewAdd: boolean = false;
public flightId: string;
public physFlightId: string;
public lookupModel: LookupModel;
public confirmStatus: string;
public createStatus: string;
public cancelStatus: string;
public editStatus: string;
public selectedTranCode: TransactionCode;
public recalculatingDuration: boolean = false;
public recalculationFromDuration: boolean = false;
constructor(private $state : ng.ui.IStateService,
private $scope: ng.IScope,
private $stateParams: TransactionDetailParams,
private $ionicPopup: ionic.popup.IonicPopupService,
private $rootScope: ng.IRootScopeService,
private transactionService: ITransactionService) {
this.model = $stateParams.transaction;
this.flightId = $stateParams.flight.Id;
this.physFlightId = $stateParams.flight.PhysFlightId;
if (this.model === null) {
this.initNewModel();
this.IsNewAdd = true;
}
this.columnConfig = $stateParams.columnConfig;
if (this.columnConfig === null) {
console.log('failed to get column config');
}
this.editors = [];
this.editors.push({Name: 'readonly', Type: 'readonly', Url: ''});
this.editors.push({Name: 'datetime', Type: 'datetime', Url: ''});
this.editors.push({Name: 'freetext', Type: 'freetext', Url: ''});
this.editors.push({Name: 'lookup', Type: 'lookup', Url: '' }) ;
this.createLookupModel();
this.buildFieldsFromModel();
if (!this.IsNewAdd) {
var trancode = new TransactionCode();
trancode.CodeType = this.model.CodeType;
this.runDisplayLogic(trancode, false);
} else {
this.editorFor(this.fields[1]);
}
}
private initNewModel() {
this.model = new TransactionDetail(this.flightId, '', '', '', 0, '', '', '', '', this.physFlightId, false, false, 1, 0);
}
public OnStartTimeChanged = (newValue, oldValue) => {
if (!this.recalculationFromDuration) {
if (oldValue.Value === newValue.Value) {
return;
}
if (this.fieldByName('EndTime').Value !== '') {
this.recalculatingDuration = true;
this.recalculateDuration();
}
}
this.recalculationFromDuration = false;
}
public OnEndTimeChanged = (newValue, oldValue) => {
if (!this.recalculationFromDuration) {
if (oldValue.Value === newValue.Value) {
return;
}
if (this.fieldByName('StartTime').Value !== '') {
this.recalculatingDuration = true;
this.recalculateDuration();
}
}
this.recalculationFromDuration = false;
}
private recalculateDuration() {
var start = moment(this.fieldByName('StartTime').Value);
var end = moment(this.fieldByName('EndTime').Value);
this.fieldByName('Duration').Value = Math.round(moment.duration(end.diff(start)).asMinutes()).toString();
}
public OnDurationChanged = (newValue, oldValue) => {
if (!this.recalculatingDuration) {
if (oldValue.Value === newValue.Value) {
return;
}
this.recalculationFromDuration = true;
if (this.fieldByName('StartTime').Value !== '') {
this.recalculateEndTimeFromDurationAndCurrentStartTime();
} else {
this.recalculateEndTimeFromStartIsNowPlusDuration();
}
}
this.recalculatingDuration = false;
}
private recalculateEndTimeFromDurationAndCurrentStartTime() {
var currentStart = moment(this.fieldByName('StartTime').Value);
this.fieldByName('EndTime').Value = currentStart.add(this.fieldByName('Duration').Value, 'minutes').toISOString();
}
private recalculateEndTimeFromStartIsNowPlusDuration() {
var startIsNow = moment(new Date());
this.fieldByName('StartTime').Value = startIsNow.toISOString();
this.fieldByName('EndTime').Value = startIsNow.add(this.fieldByName('Duration').Value, 'minutes').toISOString();
}
private addWatches() {
this.$scope.$watch('vm.fields[4]', this.OnStartTimeChanged, true);
this.$scope.$watch('vm.fields[5]', this.OnEndTimeChanged, true);
this.$scope.$watch('vm.fields[6]', this.OnDurationChanged, true);
}
private createLookupModel() {
this.lookupModel = new LookupModel();
this.lookupModel.searchString = '';
this.lookupModel.resultField = 'Name';
this.transactionService.getTransactionCodes()
.then((r) => this.onTransactionCodesLoad(r));
var self = this;
this.lookupModel.results = [];
this.lookupModel.onSelected = (selectedTransactionCode: any) => {
self.runDisplayLogic(selectedTransactionCode, true);
self.selectedTranCode = selectedTransactionCode;
};
this.lookupModel.search = () => {
this.lookupModel.results = self.lookupModel.lookupList.filter(item =>
item.Code.indexOf(self.lookupModel.searchString.toUpperCase()) !== -1 ||
item.Name.indexOf(self.lookupModel.searchString.toUpperCase()) !== -1);
};
}
private onTransactionCodesLoad(request: any) : void {
this.lookupModel.lookupList = request;
this.lookupModel.results = this.lookupModel.lookupList;
}
private findCode(value: any, searchStr: string) {
return value.Name === searchStr;
}
public runDisplayLogic(selectedTransactionCode, notCreatedYet: boolean) {
var trancode = selectedTransactionCode as TransactionCode;
var fields = this.fields as EditorField[];
if (notCreatedYet) {
this.resetFields(fields);
fields[0].Value = selectedTransactionCode.Name;
fields[1].Value = selectedTransactionCode.Code;
}
if (trancode.CodeType === TransactionType.QuantityOnly) {
fields[6].Invisible = true; //duration
fields[4].Invisible = true; //StartTime
fields[5].Invisible = true; //EndTime
} else if (trancode.CodeType === TransactionType.TimesOnly) {
fields[3].Invisible = true;
}
}
private resetFields(fields: EditorField[]) {
for (var i = 0; i < fields.length; i++) {
fields[i].Invisible = false;
if (fields[i].Editor === 'datetime') {
fields[i].Value = '';
} else {
fields[i].Value = null;
}
}
}
private confirmTransaction() {
this.buildModelFromEditorFields();
this.confirmStatus = 'P';
this.transactionService.confirmTransaction(this.model)
.then(() => {
this.confirmStatus = 'S';
this.$state.go('chroma.flight-detail.transaction-list');
});
}
private cancelTransaction() {
this.buildModelFromEditorFields();
this.cancelStatus = 'P';
this.transactionService.cancelTransaction(this.model)
.then(() => {
this.cancelStatus = 'S';
this.$state.go('chroma.flight-detail.transaction-list');
});
}
private buildFieldsFromModel() {
this.fields = [];
this.fields.push(new EditorField('Name', this.model.Name, 'readonly', '', '', '', true, false));
this.fields.push(new LookupEditorField('Code', this.model.Code, this.editorOrReadonly('FLGTTRAN_TRANCATG_CODE', 'lookup'), '', '', this.lookupModel, '', true, false));
this.fields.push(new EditorField('PONumber', this.model.PONumber, this.editorOrReadonly('FLGTTRAN_PO_NUMBER', 'freetext'), '', '', '', true, false));
this.fields.push(new EditorField('Quantity', this.model.Quantity.toString(), this.editorOrReadonly('FLGTTRAN_QUANTITY', 'freetext'), '', '', '', true, false));
this.fields.push(new EditorField('StartTime', this.model.StartTime, this.editorOrReadonly('FLGTTRAN_START_DATE_TIME', 'datetime') , '', '', '', true, false));
this.fields.push(new EditorField('EndTime', this.model.EndTime, this.editorOrReadonly('FLGTTRAN_END_DATE_TIME', 'datetime'), '', '', '', true, false));
this.fields.push(new EditorField('Duration', this.model.Duration, this.editorOrReadonly('FLGTTRAN_DURATION', 'freetext'), '', '', '', true, false));
this.addWatches();
}
private editorOrReadonly(fieldName: string, type: string) {
var editable = this.columnAccessByFieldName(fieldName);
if (editable) {
return type;
}
return 'readonly';
}
private columnAccessByFieldName(name: string) : boolean {
if (this.IsNewAdd !== true) {
return false; //can't edit fields on modify screen
}
let col : ITransactionConfig = this.columnConfig.filter(item => item.ColumnName.indexOf(name) !== - 1)[0];
return col.Editable;
}
private createTransaction() {
this.buildModelFromEditorFields();
if (this.formIsValid()) {
this.transactionService.createTransaction(this.model)
.then(() =>
this.$state.go('chroma.flight-detail.transaction-list')
);
}
}
private formIsValid() : boolean {
let editorFields = this.fields as Array<EditorField>;
var valid = true;
editorFields.forEach(element => {
if (element.Invisible === false) {
if (!this.validateQuantity(element)) {
valid = false;
}
if (!this.validateTimes(element)) {
valid = false;
}
if (!this.validateTimesAndQuantity(element)) {
valid = false;
}
}
});
return valid;
}
private validateQuantity(element: EditorField) : boolean {
if (this.selectedTranCode.CodeType === TransactionType.QuantityOnly || this.selectedTranCode.CodeType === TransactionType.TimeDurationAndQuantity) {
if (element.Name === 'Quantity' && (element.Value === '' || element.Value === null)) {
element.Invalid = true;
return false;
}
}
return true;
}
private validateTimes(element: EditorField) : boolean {
if (this.selectedTranCode.CodeType === TransactionType.TimesOnly || this.selectedTranCode.CodeType === TransactionType.TimeDurationAndQuantity) {
if ((element.Name === 'StartTime' || element.Name === 'EndTime' || element.Name === 'Duration')
&& (element.Value === '' || element.Value === null)) {
element.Invalid = true;
return false;
}
}
return true;
}
private validateTimesAndQuantity(element: EditorField) : boolean {
if (this.selectedTranCode.CodeType === TransactionType.TimeDurationAndQuantity) {
return this.validateTimes(element) && this.validateQuantity(element);
}
return true;
}
private buildModelFromEditorFields() {
var publflgt = this.model.PublflightId;
var Id = this.model.Id;
var physFlightId = this.model.PhysflightId;
var timestamp = this.model.Timestamp;
this.model = new TransactionDetail(publflgt,
Id,
this.fieldByName('Code').Value,
this.fieldByName('Name').Value,
Number(this.fieldByName('Quantity').Value),
this.fieldByName('Duration').Value,
this.fieldByName('PONumber').Value,
this.fieldByName('StartTime').Value,
this.fieldByName('EndTime').Value,
physFlightId,
this.model.Confirmed,
this.model.Cancelled, 1,
timestamp);
}
private fieldByName(name: string) : IFlightInformation {
let field : IFlightInformation = this.fields.filter(item => item.Name.indexOf(name) !== -1)[0];
return field;
}
public editorFor(field: InformationService.IFlightInformation) {
if (field.Editor.toLowerCase() === 'readonly') { return; }
let editor = field.Editor.toLowerCase();
let modalScope: any = this.$rootScope.$new();
let editorDefinition: InformationService.IFlightEditor;
this.editors.forEach(e => {
if (e.Name.toLowerCase() === editor) {
editorDefinition = e;
}
});
modalScope.model = this.model;
modalScope.field = field;
modalScope.editor = editorDefinition;
if (field.Editor.toLowerCase() === 'lookup') {
modalScope.lookupModel = this.lookupModel;
}
modalScope.instance = this.$ionicPopup.show({
templateUrl: `app/flight-detail/editors/${editor}/${editor}-holder.tpl.html`,
cssClass: `editor editor-${editor}`,
title: `Update ${field.Name}`,
scope: modalScope
});
}
}
@directive(Mod, 'chromaTransactionDetail')
export class Directive implements ng.IDirective {
controller: string = Controller.$componentName;
controllerAs: string = 'vm';
templateUrl: string = 'app/flight-detail/transactions/transaction-detail.tpl.html';
restrict: string = 'E';
replace: boolean = false;
scope: any = true;
}

View File

@ -0,0 +1,105 @@
.icon-base{
font-size: 110%;
}
.icon-status-off{
.icon-base;
color:lightgrey;
}
.icon-status-on-green{
.icon-base;
color:green;
}
.icon-status-on-red{
.icon-base;
color: red;
}
.large-descriptor{
font-size:15pt;
}
body {
cursor: url("http://ionicframework.com/img/finger.png"), auto;
}
button.button-icon.round-overlay-button {
position: absolute;
height: 56px;
width: 56px;
border-radius: 50%;
min-height: 56px;
padding: 0;
border: none;
background-color: #ffc900;
color: white;
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16), 0 2px 5px 0 rgba(0, 0, 0, 0.26);
transition: all linear 220ms;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
}
button.button-icon.round-overlay-button.energized {
background-color: #ffc900;
color: white;
}
button.button-icon.round-overlay-button.energized.activated {
background-color: #b38d00;
}
button.button-icon.round-overlay-button.assertive {
background-color: #567aae;
color: white;
}
button.button-icon.round-overlay-button.assertive.activated {
background-color: #00398a;
}
button.button-icon.round-overlay-button.positive {
background-color: #387ef5;
color: white;
}
button.button-icon.round-overlay-button.positive.activated {
background-color: #0b56d6;
}
button.button-icon.round-overlay-button.stable {
background-color: rgba(0, 0, 0, 0.26);
color: rgba(0, 0, 0, 0.26);
}
button.button-icon.round-overlay-button.stable.activated {
background-color: rgba(0, 0, 0, 0.26);
}
button.button-icon.round-overlay-button.icon:before {
font-size: 24px;
}
button.button-icon.round-overlay-button.small {
height: 40px;
width: 40px;
min-height: 40px;
padding: 0;
border: none;
}
button.button-icon.round-overlay-button.small.icon:before {
line-height: 40px;
}
button.button-icon.round-overlay-button.activated {
opacity: 1;
box-shadow: 0 6px 20px 0 rgba(0, 0, 0, 0.19), 0 8px 17px 0 rgba(0, 0, 0, 0.2);
}
button.button-icon.round-overlay-button.left {
left: 1px;
top: 16px;
}
button.button-icon.round-overlay-button.bottom-edge-right {
bottom: -24px;
right: 150px;
z-index: 12;
}
button.button-icon.round-overlay-button.bottom-right {
bottom: 0;
margin-bottom: 20px;
right: 10px;
z-index: 2;
}
button.button-icon.round-overlay-button.bottom-right.slide-down {
transform: translate3d(0, 88px, 0);
}

View File

@ -0,0 +1,138 @@
import {Directive, Controller, TransactionListParams} from './transaction-list'
import {ComponentTest} from '../../infrastructure/ComponentHelper';
import {Mod as CoreModule} from '../../core/core.mod';
import FlightDetailModule from '../../flight-detail/flight-detail.mod';
import {ITransactionDetail} from '../services/transactionService';
import {IFlightDetail} from '../../core/service/flightService';
class TransactionListTest extends ComponentTest {
public api: any;
public $stateParams: TransactionListParams;
public $state: ng.ui.IStateService;
public transactions: Array<ITransactionDetail>;
public ctrl: Controller;
public testModel: IFlightDetail;
public compileString: string = '<chroma:Transaction-List model=\'model\'></chroma:Transaction-List>';
constructor(){
super(Directive.$componentName, 'app/flight-detail/transactions/transaction-list.tpl.html');
}
public setup(): void {
this.testModel = {
Id: "123",
Location: undefined,
Operator: undefined,
Number: undefined,
AircraftType: undefined,
Registration: undefined,
Stand: undefined,
Terminal: undefined,
Actual: undefined,
Scheduled: undefined,
Estimated: undefined,
Type: 'D'
}
}
public buildTransactionList(count: number): void {
this.transactions = [];
for(let i=0; i < count; i++){
let iStr = i.toString();
this.transactions.push({
Id: iStr,
Code: iStr + iStr + iStr,
Name: 'Transaction' + i,
Duration: '250',
PONumber: 'ZZZZ' + i.toString(),
StartTime: '09:00',
EndTime: '12:4' + iStr,
Quantity: i * i,
Confirmed: true,
Cancelled:false
});
}
}
public setupForSuccessfulGet(): void {
this.$stateParams.flightId = '123';
console.log(this.transactions)
this.buildTransactionList(4);
console.log(this.transactions);
let expectedUrl = this.api.transactions + "?flightId=" + this.$stateParams.flightId;
this.$httpBackend.expectGET(expectedUrl)
.respond(201, {
Transactions: this.transactions
});
this.ctrl = this.compile<Controller>({model: this.testModel}, this.compileString);
console.log(this.ctrl);
}
}
describe('Transaction List View Tests', () => {
let instance: TransactionListTest,
core: CoreModule,
flightDetail: FlightDetailModule;
beforeEach(() => {
if(!instance) {
core = new CoreModule(angular);
flightDetail = new FlightDetailModule(angular);
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('chroma.configuration');
angular.mock.module(CoreModule.$componentName);
angular.mock.module(FlightDetailModule.$componentName);
angular.mock.inject((api, $state, $stateParams) => {
instance = new TransactionListTest();
instance.setup();
instance.api = api;
instance.$state = $state;
instance.$stateParams = $stateParams;
});
}
});
it('Directive is compiled', () => {
instance.flushOnInit = false;
instance.setup();
instance.ctrl = instance.compile<Controller>({model: this.testModel}, instance.compileString);
console.log(instance.ctrl);
expect(instance.ctrl).toBeDefined();
});
it('Flight has 4 transactions and 4 transactions are displayed', () => {
console.log(instance);
instance.setupForSuccessfulGet();
console.log(instance.ctrl);
expect(instance.ctrl).toBeDefined();
expect(instance.ctrl.transactions).toBeDefined();
expect(instance.ctrl.transactions.length).toEqual(4);
expect(instance.ctrl.transactions).toEqual(instance.transactions);
});
it('Transaction Detail shown when transaction pressed', () => {
instance.setupForSuccessfulGet();
let firstTran = instance.ctrl.transactions[0];
spyOn(instance.$state, "go");
instance.ctrl.showTransactionDetail(firstTran);
expect(instance.$state.go).toHaveBeenCalledWith('chroma.flight-detail.transaction-detail', {
transaction: firstTran
});
});
});

View File

@ -0,0 +1,69 @@
<ion-view view-title="Transaction List">
<ion-content class="padding" scroll="true" has-bounce="true">
<div class="row" ng-show="vm.loading">
<div class="col">
<ion-spinner icon="spiral" class="primary-color vcenter"></ion-spinner>
</div>
</div>
<button ng-if="vm.$stateParams.tranAccessModel.Add" ng-disabled="vm.status === 'loadingAddPage'" class="round-overlay-button bottom-right assertive button button-icon icon ion-ios7-search-strong light"
fab ng-click="vm.showAddTransaction()">
<ng-if ng-if="vm.status==='loadingAddPage'">
<ion-spinner icon="spiral" class="vcenter white"></ion-spinner>
</ng-if>
<ng-if ng-if="vm.status==='awaitingAdd'">
<i class="icon ion-plus"></i>
</ng-if>
</button>
<ion-scroll zooming="true" direction="y" style="width: auto; height: 100%">
<ion-refresher class="primary-color" pulling-text="Refresh" on-refresh="vm.load(false)" spinner="none">
</ion-refresher>
<ion-list class="flight-container" ng-show="!vm.loading">
<ion-item class="flight-detail list" collection-repeat="trn in vm.transactions" item-height="32%" item-width="100%">
<a class="item" on-tap="vm.showTransactionDetail(trn)">
<div class="row">
<div class="col text-left primary-color shunt-down-text">
<span class="large-descriptor">
{{trn.Name}}
</span>
</div>
<div class="col text-left primary-color shunt-down-text" ></div>
<div class="col">
<i ng-class="(trn.Confirmed) ? 'icon-status-on-green' : 'icon-status-off'" class="icon ion-checkmark-round" style="position:absolute; right:25px;top:5px" ></i>
<i ng-class="(trn.Cancelled) ? 'icon-status-on-red' : 'icon-status-off'" class="icon ion-close-round" style="position:absolute; right:5px; top:5px"></i>
</div>
</div>
<div class="row">
<div class="col text-left primary-color shunt-down-text">
<span class="descriptor">PONum:</span> {{trn.PONumber}} &nbsp;
</div>
<div class="col text-left primary-color shunt-down-text">
<span class="descriptor">Code:</span> {{trn.Code}} &nbsp;
</div>
<div class="col text-left primary-color shunt-down-text" ng-if="trn.CodeType == 2 || trn.CodeType == 3">
<span class="descriptor">Dur:</span> {{trn.Duration}}
</div>
<div class="col" ></div>
</div>
<div class="row">
<div class="col text-left primary-color shunt-down-text" ng-if="trn.CodeType == 1 || trn.CodeType == 3">
<span class="descriptor">Quan: </span> {{trn.Quantity}}
</div>
<div class="col text-left primary-color shunt-down-text" ng-if="trn.CodeType == 2 || trn.CodeType == 3">
<span class="descriptor">Start: </span> {{trn.StartTime}}
</div>
<div class="col" ></div>
</div>
<div class="row">
</div>
<div class="row" ng-if="trn.CodeType == 2 || trn.CodeType == 3">
<div class="col text-left primary-color shunt-down-text">
<span class="descriptor">End: </span> {{trn.EndTime}}
</div>
</div>
</a>
</ion-item>
</ion-list>
</ion-scroll>
</ion-content>
</ion-view>

View File

@ -0,0 +1,117 @@
import {controller, directive} from '../../infrastructure/Dectorators/Components';
import {Mod} from './transaction.mod';
import {ITransactionService, ITransactionDetail, ITransactionConfig, ITransactionListRequest} from '../services/transactionService';
import * as InformationService from '../services/flightInformationService';
import {IFlightDetail} from '../../core/service/flightService';
export interface TransactionListParams extends ng.ui.IStateParamsService {
flightId: string;
transaction:any;
tranAccessModel: InformationService.IUserAccessModel;
}
@controller(Mod, 'transactionListController', ['transactionService', '$stateParams', '$state', '$rootScope', 'chromaState'])
export class Controller {
public static NO_TRANSACTIONS = 'This flight has no transactions';
public transactions: Array<ITransactionDetail>;
public message: string;
public request: ITransactionListRequest;
public flightId: string;
public model: IFlightDetail;
public columnConfig: Array<ITransactionConfig>;
public signalRInitiated: boolean;
public status: string;
public loading: boolean = true;
constructor(private transactionService: ITransactionService,
private $stateParams: TransactionListParams,
private $state: ng.ui.IStateService,
private $rootScope: ng.IScope) {
this.load();
}
public load() : void {
this.status = 'loadingAddPage';
this.transactionService.getTransactionConfig(this.model.Id, this.model.PhysFlightId)
.then((colConfig) => {
this.status = 'awaitingAdd';
this.transactionService.getTransactions(this.model.Id, this.model.PhysFlightId)
.then((transactions) => this.onTransactionsLoad(transactions, colConfig));
});
}
private setupSignalRHandler = () => {
this.$rootScope.$on('recieveHandlingTransactionAdd', (event, args) => {
this.load();
console.log('transaction received in transaction list view with Id ' + args.message.TransactionId);
});
this.$rootScope.$on('recieveHandlingTransactionCancelled', (event, args) => {
if (this.transactions.length > 0) {
var matchedTransaction = this.transactions.filter(item => item.Id.indexOf(args.message.TransactionId) !== -1)[0];
if (matchedTransaction) {
matchedTransaction.Cancelled = true;
this.load();
}
}
console.log('transaction cancelled in transaction list view with Id ' + args.message.TransactionId);
});
}
private onTransactionsLoad(transactions: any, colConfig): void {
this.transactions = transactions;
this.columnConfig = colConfig;
this.loading = false;
if (this.signalRInitiated !== true) {
this.setupSignalRHandler();
this.signalRInitiated = true;
}
if (!this.transactions || this.transactions.length === 0) {
this.message = Controller.NO_TRANSACTIONS;
}
}
public showTransactionDetail(transaction: ITransactionDetail) {
if (this.columnConfig !== null) {
this.$state.go('chroma.flight-detail.transaction-detail', {
transaction: transaction,
tranAccess: this.$stateParams.tranAccessModel,
columnConfig: this.columnConfig
});
} else {
console.log('columnConfig is null');
}
}
public showAddTransaction() {
if (this.columnConfig != null) {
this.$state.go('chroma.flight-detail.transaction-detail', {
transaction: undefined,
tranAccess: this.$stateParams.tranAccessModel,
columnConfig: this.columnConfig
});
} else {
console.log('columnConfig is null');
}
}
}
@directive(Mod, 'chromaTransactionList', ['$stateParams'])
export class Directive implements ng.IDirective {
controller: string = Controller.$componentName;
controllerAs: string = 'vm';
templateUrl: string = 'app/flight-detail/transactions/transaction-list.tpl.html';
restrict: string = 'E';
replace: boolean = false;
bindToController: boolean = true;
scope: any = {
model: '='
};
}

View File

@ -0,0 +1,19 @@
import {inject} from '../../infrastructure/Dectorators/Components';
@inject(['$stateProvider'])
export class Routes {
constructor ($stateProvider: ng.ui.IStateProvider) {
$stateProvider
.state('chroma.flight-detail.transaction-detail', {
url: '/transaction-detail',
params: {
transaction: undefined
},
views: {
'detail':{
template: '<chroma:Transaction-Detail></chroma:Transaction-Detail>'
}
}
});
}
}

View File

@ -0,0 +1,21 @@
import Dectorators = require('../../infrastructure/Dectorators/Components');
import TransactionListComp = require('./transaction-list');
import TransactionDetailComp = require('./transaction-detail');
import {TransactionService} from '../services/transactionService';
import Config = require('./transaction.config');
@Dectorators.module('transaction-list')
export class Mod {
constructor (angular: ng.IAngularStatic) {
angular.module(Mod.$componentName, [])
.factory(TransactionService.$componentName, TransactionService.$factory)
.controller(TransactionListComp.Controller.$componentName, TransactionListComp.Controller)
.directive(TransactionListComp.Directive.$componentName, TransactionListComp.Directive.$factory)
.controller(TransactionDetailComp.Controller.$componentName, TransactionDetailComp.Controller)
.directive(TransactionDetailComp.Directive.$componentName, TransactionDetailComp.Directive.$factory)
.config(Config.Routes);
}
}

View File

@ -0,0 +1,16 @@
import FlightService = require('../core/service/flightService');
export class Routes {
static $inject: Array<string> = ['$stateProvider'];
constructor($stateProvider: ng.ui.IStateProvider) {
$stateProvider.state('chroma.flight-list', {
url: '/flight-list/:filter',
views: {
'content': {
template: '<chroma:Flight-List></chroma:Flight-List>'
}
}
});
}
}

View File

@ -0,0 +1,38 @@
import Dectorators = require('../infrastructure/Dectorators/Components');
import FlightListComp = require('./list/flight-list');
import Config = require('./flight-list.config');
@Dectorators.module('flight-list')
export class Mod {
constructor(angular: ng.IAngularStatic) {
angular.module(Mod.$componentName, [])
.controller(FlightListComp.Controller.$componentName, FlightListComp.Controller)
.directive(FlightListComp.Directive.$componentName, FlightListComp.Directive.$factory)
.filter('fieldListFilter', () => {
return (items: any, query: any, fields: any) => {
if (items == null || query == null || fields == null) {
return items;
}
var filtered = [];
var letterMatch = new RegExp(query, 'i');
for (var i = 0; i < items.length; i++) {
var item = items[i];
for (var j = 0; j < fields.length; j++) {
if (query && query.length > 0) {
if (letterMatch.test(item[fields[j]].substring(0, query.length))) {
filtered.push(item);
}
} else {
filtered.push(item);
}
}
}
return filtered;
};
})
.config(Config.Routes);
}
}

View File

@ -0,0 +1,88 @@
@arrivalColor: #f44336;
@departureColor: #1565c0;
.search-container {
margin-top: 5px;
}
.search-input {
padding-bottom: 0;
}
.padding-top {
padding-top: 5px;
}
.flight-detail {
padding-right: 0;
&.flight-header {
padding: 10px 0 10px 0 !important;
font-size: 110%;
img {
top: 14px !important;
left: 10px !important;
max-width: 60px !important;
max-height: 60px !important;
}
}
img {
top: 14px !important;
left: 10px !important;
max-width: 50px !important;
max-height: 50px !important;
}
.arrival {
border: 1px solid @arrivalColor;
.ind-text {
color: @arrivalColor;
small {
font-size: 75%;
}
}
}
.departure {
border: 1px solid @departureColor;
.ind-text {
color: @departureColor;
small {
font-size: 75%;
}
}
}
.descriptor {
font-family: Roboto-Thin;
color: #d2d2d2;
}
.item {
color: #b6b5b5;
border-color: transparent;
}
.item {
.row {
padding: 0;
padding-top: 5px;
.col {
padding: 0;
}
}
}
.shunt-down-text {
margin-top: 3px;
}
}
.ionic-refresher-content {
color: #00398a;
}

View File

@ -0,0 +1,268 @@
import {Mod as CoreModule} from '../../core/core.mod';
import {Mod as FlightListModule} from '../flight-list.mod';
import FlightDetailModule from '../../flight-detail/flight-detail.mod';
import {Directive, Controller, FlightListParams} from './flight-list';
import {IFlightDetail} from './../../core/service/flightService';
import {ComponentTest} from'../../infrastructure/ComponentHelper';
class FlightListTest extends ComponentTest {
public api: any;
public $stateParams: FlightListParams;
public $state: ng.ui.IStateService;
public flights: Array<IFlightDetail>;
public ctrl: Controller;
constructor() {
super(Directive.$componentName,
'app/flight-list/list/flight-list.tpl.html');
}
public buildFlights(count: number): void {
this.flights = [];
for (let i = 0; i < count; i++) {
this.flights.push({
Id: '1', Type: 'A', Operator: 'SK', Number: '0214' + i, AircraftType: '333' + i,
Registration: 'PK-GPE' + i, Location: 'MAN', Scheduled: '09:00', Estimated:
'09:00', Actual: '09:00', Terminal: 'T1', Stand: 'A' + i, FlightConcat: 'SK' + '0214' + i
});
}
}
public setUpForSuccessfulGet(): void {
this.$stateParams.filter = "default";
this.buildFlights(5);
let expectedUrl = this.api.flightList + "?window=" + this.$stateParams.filter;
this.$httpBackend.expectGET(expectedUrl)
.respond(201, { Flights: this.flights, RequestId: 'ste1234567' });
this.ctrl = this.compile<Controller>();
}
public setUpForNoFlightsGet(): void {
this.$stateParams.filter = "default";
let expectedUrl = this.api.flightList + "?window=" + this.$stateParams.filter;
this.$httpBackend.expectGET(expectedUrl)
.respond(201, { Flights: new Array<any>(), RequestId: 'ste1234567' });
this.ctrl = this.compile<Controller>();
}
}
describe('Framework: fieldListFilter', () => {
let $filter: any;
let instance: FlightListTest,
flightList: FlightListModule,
core: CoreModule,
flightDetail: FlightDetailModule;
beforeEach(() => {
if (!instance) {
core = new CoreModule(angular);
flightList = new FlightListModule(angular);
flightDetail = new FlightDetailModule(angular);
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('chroma.configuration');
angular.mock.module(CoreModule.$componentName);
angular.mock.module(FlightDetailModule.$componentName);
angular.mock.module(FlightListModule.$componentName);
angular.mock.inject((_$filter_, $state, $stateParams, api) => {
instance = new FlightListTest();
$filter = _$filter_;
instance.$state = $state;
instance.$stateParams = $stateParams;
instance.api = api;
});
}
});
it('Flight Filter: should return flight SK02141 when I search with SK02141', () => {
instance.setUpForSuccessfulGet();
instance.buildFlights(5);
let result = $filter('fieldListFilter')(instance.ctrl.flights, 'SK02141', ['Number',
'Operator', 'AircraftType', 'Location', 'Terminal', 'Registration',
'Stand', 'FlightConcat']);
expect(result.length).toBe(1);
expect(result[0].FlightConcat).toBe('SK02141');
});
it('fieldListFilter: filter should be registered', () => {
let result = $filter('fieldListFilter');
expect(result).not.toBeUndefined();
});
it('fieldListFilter: empty query string should return all flight results', () => {
instance.buildFlights(5);
let result = $filter('fieldListFilter')(instance.ctrl.flights, '', ['Number',
'Operator', 'AircraftType', 'Location', 'Terminal', 'Registration',
'Stand', 'FlightConcat']);
expect(instance.ctrl.flights.length).toBe(5);
});
it('fieldListFilter: should return flight with stand A1 when I search for A1', () => {
instance.setUpForSuccessfulGet();
instance.buildFlights(5);
let result = $filter('fieldListFilter')(instance.ctrl.flights, 'A1', ['Number',
'Operator', 'AircraftType', 'Location', 'Terminal', 'Registration',
'Stand', 'FlightConcat']);
expect(result.length).toBe(1);
expect(result[0].Stand).toBe('A1');
});
it('fieldListFilter: should return flight with aircraft type 331 when I search for 331', () => {
instance.setUpForSuccessfulGet();
instance.buildFlights(5);
let result = $filter('fieldListFilter')(instance.ctrl.flights, '3331', ['Number',
'Operator', 'AircraftType', 'Location', 'Terminal', 'Registration',
'Stand', 'FlightConcat']);
expect(result.length).toBe(1);
expect(result[0].AircraftType).toBe('3331');
});
it('fieldListFilter: should return flight with registration PK-GPE2 when I search for PK-GPE2', () => {
instance.setUpForSuccessfulGet();
instance.buildFlights(5);
let result = $filter('fieldListFilter')(instance.ctrl.flights, 'PK-GPE2', ['Number',
'Operator', 'AircraftType', 'Location', 'Terminal', 'Registration',
'Stand', 'FlightConcat']);
expect(result.length).toBe(1);
expect(result[0].Registration).toBe('PK-GPE2');
});
it('fieldListFilter: should return all flights when I search for PK-GPE as they all start with that', () => {
instance.setUpForSuccessfulGet();
instance.buildFlights(5);
let result = $filter('fieldListFilter')(instance.ctrl.flights, 'PK-GPE', ['Number',
'Operator', 'AircraftType', 'Location', 'Terminal', 'Registration',
'Stand', 'FlightConcat']);
expect(result.length).toBe(5);
});
it('fieldListFilter: should return all flights when I search T1 as they are all assigned that terminal', () => {
instance.setUpForSuccessfulGet();
instance.buildFlights(5);
let result = $filter('fieldListFilter')(instance.ctrl.flights, 'T1', ['Number',
'Operator', 'AircraftType', 'Location', 'Terminal', 'Registration',
'Stand', 'FlightConcat']);
expect(result.length).toBe(5);
});
});
describe('Flight List View Tests', () => {
let instance: FlightListTest,
core: CoreModule,
flightDetail: FlightDetailModule,
flightList: FlightListModule;
beforeEach(() => {
if (!instance) {
core = new CoreModule(angular);
flightDetail = new FlightDetailModule(angular);
flightList = new FlightListModule(angular);
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('chroma.configuration');
angular.mock.module(CoreModule.$componentName);
angular.mock.module(FlightDetailModule.$componentName);
angular.mock.module(FlightListModule.$componentName);
angular.mock.inject((api, $state, $stateParams) => {
instance = new FlightListTest();
instance.api = api;
instance.$state = $state;
instance.$stateParams = $stateParams;
});
}
});
it('Filter has 5 flights and 5 flights displayed', () => {
instance.setUpForSuccessfulGet();
expect(instance.ctrl).toBeDefined();
expect(instance.ctrl.flights).toBeDefined();
expect(instance.ctrl.flights.length).toEqual(5);
expect(instance.ctrl.flights).toEqual(instance.flights);
});
it('New flight is added when refreshed', () => {
instance.setUpForSuccessfulGet();
var currentCount = instance.ctrl.flights.length;
instance.buildFlights(6);
let expectedUrl = instance.api.flightList + "?window=" + instance.$stateParams.filter;
instance.$httpBackend.expectGET(expectedUrl)
.respond(201, { Flights: instance.flights, RequestId: 'ste1234567' });
instance.ctrl.load(false);
instance.$httpBackend.flush();
expect(instance.ctrl.flights.length).toBe(6);
expect(instance.ctrl.flights.length).toBeGreaterThan(currentCount);
});
it('Detail shown when flight pressed', () => {
let firstFlight = instance.flights[0],
requestId = 'ste1234567';
spyOn(instance.$state, "go");
instance.ctrl.showDetail(firstFlight);
expect(instance.$state.go).toHaveBeenCalledWith('chroma.flight-detail', {
id: firstFlight.Id,
flight: firstFlight,
requestId: requestId
});
});
it('No flights error message displayed', () => {
instance.setUpForNoFlightsGet();
expect(instance.ctrl.flights.length).toBe(0);
expect(instance.ctrl.message).toBe(Controller.NO_FLIGHTS_MESSAGE);
});
});

View File

@ -0,0 +1,66 @@
<ion-view view-title="Flight List">
<ion-content class="padding" scroll="true" has-bounce="true">
<div ng-show="vm.message" class="row row-center">
<h4 class="col text-center">
<div class="font-thin primary-color">{{vm.message}}</div>
<a class="primary-color" ui-sref="chroma.window-list">Go back?</a>
</h4>
</div>
<div ng-show="!vm.message">
<div class="list list-inset search-container">
<label class="search-input item item-input">
<i class="icon ion-search placeholder-icon"></i>
<input type="text" ng-model="vm.query">
</label>
</div>
<div class="row" ng-show="vm.loading">
<div class="col">
<ion-spinner icon="spiral" class="primary-color vcenter"></ion-spinner>
</div>
</div>
<ion-scroll zooming="false" direction="y" style="width: auto; height: 80%">
<ion-refresher class="primary-color" pulling-text="Refresh" on-refresh="vm.load(false)" spinner="none">
</ion-refresher>
<ion-list class="flight-container" ng-show="!vm.loading">
<ion-item class="flight-detail list" collection-repeat="flt in vm.flights | fieldListFilter: vm.query: ['Number', 'Operator', 'AircraftType', 'Location', 'Terminal', 'Registration', 'Stand', 'FlightConcat']" item-height="80px" item-width="100%">
<a class="item item-avatar" on-tap="vm.showDetail(flt)">
<img ng-src="{{vm.flightInformationService.getOperatorUrl(flt.Operator);}}">
<h2>
<span class="primary-color">{{flt.Operator}}</span>{{flt.Number}}
<span class="primary-color">/</span> {{flt.Location}}
<span class="primary-color">/</span> {{flt.AircraftType}}
<span class="pull-right" ng-switch="vm.flightInformationService.getTimeDisplayEnum(flt)">
<span ng-switch-when="A">A: {{flt.Actual | chromaDateFilter}}</span>
<span ng-switch-when="E">E: {{flt.Estimated | chromaDateFilter}}</span>
<span ng-switch-when="S">S: {{flt.Scheduled | chromaDateFilter}}</span>
</span>
</h2>
<div class="row">
<div class="col text-left primary-color shunt-down-text">
<span class="descriptor">R:</span> {{flt.Registration}}
</div>
<div class="col text-center primary-color shunt-down-text">
<span class="descriptor">T:</span> {{flt.Terminal}}
</div>
<div class="col text-center primary-color shunt-down-text">
<span class="descriptor">S: </span> {{flt.Stand}}
</div>
<div class="col text-right" ng-class="{arrival: flt.Type === 'A', departure: flt.Type === 'D'}">
<div class="ind-text text-center" ng-if="flt.Type === 'A'">
<small>Inbound</small>
</div>
<div class="ind-text text-center" ng-if="flt.Type === 'D'">
<small>Outbound</small>
</div>
</div>
</div>
</a>
</ion-item>
</ion-list>
</ion-scroll>
</div>
</ion-content>
</ion-view>

View File

@ -0,0 +1,67 @@
import {controller, directive} from '../../infrastructure/Dectorators/Components';
import {FlightService, IFlightDetail, IFlightListRequest, IFlightService} from '../../core/service/flightService';
import {IFlightInformationService} from '../../flight-detail/services/flightInformationService';
import {Mod} from '../flight-list.mod';
export interface FlightListParams extends ng.ui.IStateParamsService {
filter: string;
}
@controller(Mod, 'flightListController', ['flightService', '$stateParams', '$state', '$window', 'flightInformationService'])
export class Controller {
public static NO_FLIGHTS_MESSAGE = 'This window has no flights';
public flights: Array<IFlightDetail>;
public request: IFlightListRequest;
public loading: boolean = true;
public window: string;
public message: string;
constructor(private flightService: IFlightService,
private $stateParams: FlightListParams,
private $state: ng.ui.IStateService,
private $window: ng.IWindowService,
public flightInformationService: IFlightInformationService) {
this.window = this.$stateParams.filter;
this.$window.sessionStorage.setItem('chroma:current-filter', this.window);
this.load();
}
public load(initial: boolean = true): void {
if (!initial) {
this.loading = true;
}
this.flightService.getFilter(this.window, !initial)
.then((r) => this.onFilterLoad(r));
}
private onFilterLoad(request: any): void {
this.request = request;
this.flights = request.Flights;
if (!this.flights || this.flights.length === 0) {
this.message = Controller.NO_FLIGHTS_MESSAGE;
}
this.loading = false;
}
public showDetail(flight: IFlightDetail): void {
this.$state.go('chroma.flight-detail', {
id: flight.Id,
flight: flight,
requestId: this.request.RequestId
});
}
}
@directive(Mod, 'chromaFlightList', ['$stateParams'])
export class Directive implements ng.IDirective {
controller: string = Controller.$componentName;
controllerAs: string = 'vm';
templateUrl: string = 'app/flight-list/list/flight-list.tpl.html';
restrict: string = 'E';
replace: boolean = false;
scope: any = true;
}

47
app/index.html Normal file
View File

@ -0,0 +1,47 @@
<html lang="en">
<head>
<title>Chroma AODB</title>
<link href="css/lib/ionic.css" rel="stylesheet">
<link href="css/lib/chroma-framework.css" rel="stylesheet" />
<link href="css/chroma.compiled.css" rel="stylesheet" />
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap://ready file://* ws://* *; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' ws://* wss://*;">
</head>
<body>
<ion-nav-view class="chroma-app" cache-view="false"></ion-nav-view>
<script src="scripts/lib/jquery.js"></script>
<script src="scripts/lib/jquery.signalR-2.2.1.js"></script>
<!--<script src="signalr/hubs" type="text/javascript"></script> !-->
<script src="scripts/lib/moment.js"></script>
<script src="cordova.js"></script>
<script src="scripts/lib/push.js"></script>
<script src="scripts/lib/ionic.bundle.js"></script>
<script src="scripts/lib/angular-ui-router.js"></script>
<script src="scripts/lib/angular-cookies.js"></script>
<script src="scripts/lib/angular-ios9-uiwebview.patch.js"></script>
<script src="scripts/chroma.templates.compiled.js"></script>
<script src="scripts/lib/chroma-framework.js"></script>
<script src="scripts/app.config.js"></script>
<script type="text/javascript" src="scripts/lib/system.js"></script>
<script>
//window.debuging = false;
System.config({
defaultJSExtensions: true,
"baseURL": "./js/",
"paths": {
"*": "*.js"
}
});
System.import('chroma.app').then(function(chroma) {
chroma.App(angular);
});
</script>
</body>
</html>

View File

@ -0,0 +1,72 @@
class BaseComponentTest {
public $compile: ng.ICompileService;
public $rootScope: ng.IRootScopeService;
public $httpBackend: ng.IHttpBackendService;
public $templateCache: ng.ITemplateCacheService;
constructor() {
angular.mock.inject(($compile, $rootScope, $httpBackend, $templateCache) => {
this.$compile = $compile;
this.$rootScope = $rootScope;
this.$httpBackend = $httpBackend;
this.$templateCache = $templateCache;
});
}
}
export class ComponentTest extends BaseComponentTest {
public templateURL: string;
public componentName: string;
public flushOnInit: boolean;
constructor(componentName: string, templateURL: string) {
super();
this.componentName = componentName;
this.templateURL = templateURL;
this.flushOnInit = true;
this.$httpBackend.expectGET(this.templateURL)
.respond(201, this.$templateCache.get(this.templateURL));
}
public compile<T>(args: any = null, customCompileString: string = null): T {
let actCompName = this.componentName.replace(/([a-z])([A-Z])/g, '$1:$2');
let scope: any = this.$rootScope.$new();
let dir: any;
if (args) {
console.log(args);
angular.extend(this.$rootScope, args);
if (customCompileString) {
dir = this.$compile(angular.element(customCompileString))(scope);
} else {
console.log(args);
dir = this.$compile(angular.element(`<${actCompName}></${actCompName}>`))(scope);
console.log(dir);
}
scope.$digest();
if (this.flushOnInit) {
this.$httpBackend.flush();
}
} else {
dir = this.$compile(angular.element(`<${actCompName}></${actCompName}>`))(scope);
scope.$digest();
if (this.flushOnInit) {
this.$httpBackend.flush();
}
}
if (dir.controller(this.componentName) === undefined) {
console.log(this.componentName);
console.log(dir);
} else {
console.log(dir);
}
return dir.controller(this.componentName);
}
}

View File

@ -0,0 +1,88 @@
function log(text: string) {
let w: any = <any> window;
if (w.debuging) {
console.log(text);
}
}
export function inject(deps: Array<string>) {
return (obj: any) => {
obj.$inject = deps;
};
}
export function module(name: string, dep: Array<string> = []) {
return (obj: any) => {
let modName = 'chroma.' + name;
obj.$componentName = modName;
log(`${modName} registered`);
};
}
export function service(mod: any, name: string, dep: Array<string> = []) {
return (obj: any) => {
obj.$inject = dep;
obj.$componentName = name;
log(`Service/Provider ${name} registered.`);
};
}
export function controller(mod: any, name: string, dep: Array<string> = []) {
return (obj: any) => {
obj.$inject = dep;
obj.$componentName = name;
log(`Controller ${name} registered.`);
};
}
export function factory(mod: any, name: string, dep: Array<string> = []) {
return (obj: any) => {
obj.$componentName = name;
obj.$inject = dep;
const factory: Function = function(...args: any[]) {
var instance = Object.create(obj.prototype);
instance.constructor.apply(instance, arguments);
return instance;
};
factory.$inject = dep;
obj.$factory = factory;
log(`Component ${name} registered.`);
};
}
export function directive(mod: any, name: string, dep: Array<string> = []) {
return (obj: any) => {
obj.$componentName = name;
obj.$inject = dep;
const factory: Function = function(...args: any[]) {
var instance = Object.create(obj.prototype);
instance.constructor.apply(instance, arguments);
return instance;
};
factory.$inject = dep;
obj.$factory = factory;
log(`Component ${name} registered.`);
};
}

View File

@ -0,0 +1,45 @@
import {factory} from '../../infrastructure/Dectorators/Components';
import {Mod} from '../../core/core.mod';
export interface ISite {
siteId: string;
siteName: string;
}
export interface ISiteReponseData {
error: string;
}
export interface ISiteSelectionService {
setSite(siteId: number): ng.IPromise<ISiteReponseData>;
getSitesFromStorage(): Array<ISite>;
storeAvailableSites(sites: Array<ISite>);
}
@factory(Mod, 'siteSelectionService', ['$http', '$q', '$window', 'api'])
export class SiteSelectionService implements ISiteSelectionService {
constructor(private $http: ng.IHttpService,
private $q: ng.IQService,
private $window: ng.IWindowService,
private api: any) {
}
public setSite(siteId: number): ng.IPromise<ISiteReponseData> {
var data = { SelectedSiteId: siteId };
return this.$http.post(this.api.setSite, data)
.then((response: ng.IHttpPromiseCallbackArg<ISiteReponseData>) => {
if (response.data) {
return response.data;
}
});
}
public getSitesFromStorage(): Array<ISite> {
return <Array<ISite>>JSON.parse(this.$window.sessionStorage.getItem('chroma:site-list'));
}
public storeAvailableSites(sites: Array<ISite>) {
this.$window.sessionStorage.setItem('chroma:site-list', JSON.stringify(sites));
}
}

View File

@ -0,0 +1,14 @@
export class Routes {
static $inject: Array<string> = ['$stateProvider'];
constructor($stateProvider: ng.ui.IStateProvider) {
$stateProvider.state('chroma.site-selection', {
url: '/site-selection',
views: {
'content': {
template: '<chroma:Site-Selection></chroma:Site-Selection>'
}
}
});
}
}

View File

@ -0,0 +1,15 @@
import {module} from '../infrastructure/Dectorators/Components';
import Config = require('./site-selection.config');
import SiteSelectionComp = require('./view/site-selection');
import{SiteSelectionService} from './service/site-selection.service';
@module('site-selection')
export class Mod {
constructor(angular: ng.IAngularStatic) {
angular.module(Mod.$componentName, [])
.controller(SiteSelectionComp.Controller.$componentName, SiteSelectionComp.Controller)
.directive(SiteSelectionComp.Directive.$componentName, SiteSelectionComp.Directive.$factory)
.factory(SiteSelectionService.$componentName, SiteSelectionService.$factory)
.config(Config.Routes);
}
}

View File

@ -0,0 +1,101 @@
import {Mod as CoreModule} from '../../core/core.mod';
import {Mod as SiteSelectionMod} from '../site-selection.mod';
import {ComponentTest} from '../../infrastructure/ComponentHelper';
import {Directive, Controller} from './site-selection';
import {SiteSelectionService} from '../service/site-selection.service';
import {Mod as FlightListMod} from '../../flight-list/flight-list.mod';
import {Mod as WindowListMod} from '../../window-list/window-list.mod';
class SiteSelectionTest extends ComponentTest {
public api: any;
public $state: ng.ui.IStateService;
public siteSelectionService: SiteSelectionService;
constructor() {
super(Directive.$componentName,
'app/site-selection/view/site-selection.tpl.html');
}
}
describe('Site selection view Tests', () => {
let instance: SiteSelectionTest,
core: CoreModule,
siteSelection: SiteSelectionMod,
flightList: FlightListMod,
windowList: WindowListMod;
let ctrl: Controller;
beforeEach(() => {
if (!instance) {
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('chroma.configuration');
angular.mock.module(CoreModule.$componentName);
angular.mock.module(SiteSelectionMod.$componentName);
angular.mock.module(FlightListMod.$componentName);
angular.mock.module(WindowListMod.$componentName);
core = new CoreModule(angular);
siteSelection = new SiteSelectionMod(angular);
flightList = new FlightListMod(angular);
windowList = new WindowListMod(angular);
angular.mock.inject((api, $state, siteSelectionService) => {
instance = new SiteSelectionTest();
instance.api = api;
instance.$state = $state;
instance.siteSelectionService = siteSelectionService;
});
}
});
it('Directive is compiled', () => {
instance.flushOnInit = true;
ctrl = instance.compile<Controller>();
expect(ctrl).toBeDefined();
});
it('There are 4 sites to select therefore 4 are displayed', () => {
//GIVEN I have acess to 4 sites
spyOn(instance.siteSelectionService, 'getSitesFromStorage').and.returnValue(
[{ SiteName: 'testSite1', SiteId: '1', IATACode: 'TES' },
{ SiteName: 'testSite2', SiteId: '2', IATACode: 'TEZ' },
{ SiteName: 'testSite3', SiteId: '3', IATACode: 'TEZ3' },
{ SiteName: 'testSite4', SiteId: '4', IATACode: 'TEZ4' }]
);
//WHEN I am asked to select a site
instance.flushOnInit = false;
ctrl = instance.compile<Controller>();
//THEN I should have 4 sites to chose from
expect(ctrl.sites.length).toEqual(4);
});
it('Site is sucessfully selected flight list should be loaded', () => {
instance.flushOnInit = false;
ctrl = instance.compile<Controller>();
instance.$httpBackend.whenPOST(instance.api.setSite).respond(200, {
Data: {
Error: false,
}
});
spyOn(instance.$state, 'go').and.callThrough();
ctrl.setSite(23);
instance.$httpBackend.flush();
expect(instance.$state.go).toHaveBeenCalledWith('chroma.window-list');
});
it('Site selection is not Successful error should be displayed', () => {
instance.flushOnInit = false;
ctrl = instance.compile<Controller>();
instance.$httpBackend.expectPOST(instance.api.setSite).respond(200, {
Data: {
Error: true,
}
});
ctrl.setSite(23);
instance.$httpBackend.flush();
expect(ctrl.error).toBe('problem selecting site');
});
});

View File

@ -0,0 +1,18 @@
<ion-view view-title="Site Select">
<ion-content>
<div class="row" ng-show="vm.loading">
<div class="col">
<ion-spinner icon="spiral" class="primary-color vcenter"></ion-spinner>
</div>
</div>
<ion-scroll zooming="false" style="width: auto; height: 95%" padding="true">
<ion-list class="list chroma-list">
<ion-item class="item chroma-list-item" ng-repeat="site in vm.sites" style="margin-bottom: 12px" ng-click="vm.setSite(site.SiteId);">
<div class="text-center primary-color">
{{site.SiteName}}
</div>
</ion-item>
</ion-list>
</ion-scroll>
</ion-content>
</ion-view>

View File

@ -0,0 +1,35 @@
import {controller, directive} from '../../infrastructure/Dectorators/Components';
import {Mod} from '../site-selection.mod';
import {SiteSelectionService, ISiteSelectionService, ISite} from '../service/site-selection.service';
@controller(Mod, 'siteSelectionController', ['siteSelectionService', '$state'])
export class Controller {
public sites: Array<ISite>;
public error: string;
constructor(private siteSelectionService: ISiteSelectionService,
private $state: ng.ui.IStateService) {
this.sites = siteSelectionService.getSitesFromStorage();
}
public setSite(siteId: number) {
this.siteSelectionService.setSite(siteId).then((response: any) => {
if (response.Data.Error) {
this.error = 'problem selecting site';
} else {
this.$state.go('chroma.window-list');
}
});
}
}
@directive(Mod, 'chromaSiteSelection')
export class Directive implements ng.IDirective {
controller: string = Controller.$componentName;
controllerAs: string = 'vm';
templateUrl: string = 'app/site-selection/view/site-selection.tpl.html';
restring: string = 'E';
replace: boolean = false;
scope: any = true;
}

View File

@ -0,0 +1,84 @@
import {Mod as CoreModule} from '../../core/core.mod';
import {Mod as WindowListMod} from '../window-list.mod';
import {ComponentTest} from '../../infrastructure/ComponentHelper';
import {Directive, Controller, IFlightWindow} from './window-list';
class WindowListTest extends ComponentTest {
public api: any;
public $state: ng.ui.IStateService;
constructor() {
super(Directive.$componentName, 'app/window-list/list/window-list.tpl.html');
}
}
describe('Window list view Tests', () => {
let instance: WindowListTest,
core: CoreModule,
windowList: WindowListMod,
ctrl: Controller;
beforeEach(() => {
if (!instance) {
angular.mock.module('ui.router');
angular.mock.module('ionic');
angular.mock.module('chroma.configuration');
angular.mock.module(CoreModule.$componentName);
angular.mock.module(WindowListMod.$componentName);
core = new CoreModule(angular);
windowList = new WindowListMod(angular);
angular.mock.inject((api, $state) => {
instance = new WindowListTest();
instance.api = api;
instance.$state = $state;
});
}
});
it('Directive is compiled', () => {
instance.$httpBackend.expectGET(instance.api.getWindows).respond(200, []);
ctrl = instance.compile<Controller>();
expect(ctrl).toBeDefined();
expect(ctrl.windows).toBeDefined();
expect(ctrl.windows.length).toBe(0);
});
it('2 Windows are displayed and reversed', () => {
let windows: Array<IFlightWindow> = [{ Name: '1' }, { Name: '2' }];
instance.$httpBackend.expectGET(instance.api.getWindows).respond(200, windows);
ctrl = instance.compile<Controller>();
expect(ctrl.windows.length).toBe(2)
expect(ctrl.windows[0].Name).toBe('2');
})
it('2 Windows are displayed and reversed', () => {
let windows: Array<IFlightWindow> = [{ Name: '1' }, { Name: '2' }];
instance.$httpBackend.expectGET(instance.api.getWindows).respond(200, windows);
ctrl = instance.compile<Controller>();
expect(ctrl.windows.length).toBe(2)
expect(ctrl.windows[0].Name).toBe('2');
})
it('Flight list state is called on goToWindow', () => {
spyOn(instance.$state, 'go').and.returnValue(false);
instance.$httpBackend.expectGET(instance.api.getWindows).respond(200, []);
ctrl = instance.compile<Controller>();
ctrl.goToWindow({Name : 'test-123'});
expect(instance.$state.go).toHaveBeenCalledWith('chroma.flight-list', {filter : 'test-123'});
})
});

View File

@ -0,0 +1,33 @@
<ion-view view-title="Window Select">
<ion-content>
<div class="row" ng-show="vm.loading">
<div class="col">
<ion-spinner icon="spiral" class="primary-color vcenter"></ion-spinner>
</div>
</div>
<ion-scroll zooming="false" style="width: auto; height: 90%" padding="true">
<ion-list class="list chroma-list" >
<div class="card" ng-repeat="window in vm.windows" style="margin: 0 0 10px 0;" ng-click="vm.goToWindow(window);">
<div class="item item-divider">
{{window.Name}}
<div class="pull-right" ng-show="vm.currentWindow == window.Name">
<i class="icon ion-checkmark-circled" style="color: green;"></i>
</div>
</div>
<div class="item item-text-wrap">
<div ng-if="window.Name == 'Default'">
Chroma default flight filter
</div>
{{window.Description}}
<div class="pull-right">
<small style="color: #E0E0E0;">{{window.Type}}</small>
</div>
</div>
</div>
</ion-list>
</ion-scroll>
</ion-content>
</ion-view>

View File

@ -0,0 +1,44 @@
import {controller, directive} from '../../infrastructure/Dectorators/Components';
import {Mod} from '../window-list.mod';
import {ISignalrService} from '../../core/service/signalRService';
export interface IFlightWindow {
Name: string;
}
@controller(Mod, 'windowListController', ['signalrService', '$rootScope', '$http', '$state', '$window', 'api'])
export class Controller {
public loading: boolean;
public currentWindow:string;
public windows: Array<IFlightWindow>;
constructor( private signalrService: ISignalrService,
private $rootScope: ng.IScope,
private $http: ng.IHttpService,
private $state: ng.ui.IStateService,
private $window: ng.IWindowService,
private api: any) {
this.currentWindow = $window.sessionStorage.getItem('chroma:current-filter');
this.$http.get(api.getWindows).success((response: Array<IFlightWindow>) => {
this.windows = response.reverse();
});
}
public goToWindow(window: IFlightWindow): void {
this.signalrService.start($, this.signalRCallback, this.$rootScope);
this.$state.go('chroma.flight-list', { filter: window.Name });
}
public signalRCallback(event: string, scope: any, message: any) {
scope.$broadcast(event, {message: message});
}
}
@directive(Mod, 'chromaWindowList')
export class Directive implements ng.IDirective {
controller: string = Controller.$componentName;
controllerAs: string = 'vm';
templateUrl: string = 'app/window-list/list/window-list.tpl.html';
restring: string = 'E';
replace: boolean = false;
scope: any = true;
}

View File

@ -0,0 +1,21 @@
import {module} from '../infrastructure/Dectorators/Components';
import {Controller, Directive} from './list/window-list';
@module('window-list')
export class Mod {
constructor(angular: ng.IAngularStatic) {
angular.module(Mod.$componentName, [])
.controller(Controller.$componentName, Controller)
.directive(Directive.$componentName, Directive.$factory)
.config(($stateProvider: ng.ui.IStateProvider) => {
$stateProvider.state('chroma.window-list', {
url: '/window-list',
views: {
'content': {
template: '<chroma:Window-List></chroma:Window-List>'
}
}
});
});
}
}

29
bower.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "chroma.cordova",
"version": "0.0.0",
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"angular": "~1.3.15",
"angular-ui-router": "~0.2.13",
"angular-animate": "~1.3.15",
"angular-mocks": "~1.3.15",
"jasmine-jquery": "~2.1.0",
"angular-resource": "*",
"moment": "*"
},
"resolutions": {
"angular": ">=1.3.8"
},
"devDependencies": {
"ionic-service-core": "~0.1.10",
"ngCordova": "~0.1.20-alpha",
"ionic-service-push": "~0.1.13"
}
}

14
build.json Normal file
View File

@ -0,0 +1,14 @@
{
"ios": {
"debug": {
"developmentTeam":"DA3XCEKC7Z",
"provisioningProfile" : "1a1555b1-8483-466e-abd4-1822170b5a4c"
},
"release": {
"developmentTeam":"DA3XCEKC7Z",
"provisioningProfile" : "1a1555b1-8483-466e-abd4-1822170b5a4c"
}
}
}

View File

@ -0,0 +1,6 @@
param(
[String] $APIURL = ""
)
(get-content app.config.js.token).Replace('__APIURL__', $APIURL) | set-content app.config.js

View File

@ -0,0 +1,49 @@

var base = "http://10.14.64.82:89";
angular.module('chroma.configuration', []).constant('api', {
//STE OATES MACHINE
// endpoint: 'http://10.14.30.91/api/',l
// flightList: 'http://10.14.30.91/api/flights',
// authentication: 'http://10.14.30.91/api/auth',
// detail: 'http://10.14.30.91/api/detail/',
// imageSource: 'http://10.14.30.91/api/GetOperatorImage',
// setSite: 'http://10.14.30.91/api/setSite',
// getWindows: 'http://10.14.30.91/api/getWindows'
//JAMES DILCOCK MACHINE
endpoint: base + '/api/',
flightList: base + '/api/flights',
authentication: base +'/api/auth',
detail: base + '/api/detail/',
imageSource: base + '/api/GletOperatorImage',
setSite: base + '/api/setSite',
getWindows: base + '/api/getWindows',
transactions: base + '/api/transactions',
cancelTransaction: base + '/api/cancelTransaction',
confirmTransaction: base + '/api/confirmTransaction',
createTransaction: base + '/api/createTransaction',
getTransactionCodes: base + '/api/getTransactionCodes',
getTransactionsAccess: base + '/api/getUserAccessRightsForTransaction',
getTransactionConfig: base + '/api/getTransactionConfig',
signalrHubs: base + '/signalr/hubs'
//TESTING MACHINE
// endpoint: 'http://t-t-v-chrapp14:81/api/',
// flightList: 'http://t-t-v-chrapp14:81/api/flights',
// authentication: 'http://t-t-v-chrapp14:81/api/auth',
// detail: 'http://t-t-v-chrapp14:81/api/detail/',
// imageSource: 'http://t-t-v-chrapp14:81/api/GetOperatorImage',
// setSite: 'http://t-t-v-chrapp14:81/api/setSite',
// getWindows: 'http://t-t-v-chrapp14:81/api/getWindows'
//ADE CLOUD CHROMA END POINT
//endpoint: 'http://chromademo.cloudapp.net:81/api/',
//flightList: 'http://chromademo.cloudapp.net:81/api/flights',
//authentication: 'http://chromademo.cloudapp.net:81/api/auth',
//detail: 'http://chromademo.cloudapp.net:81/api/detail/',
//imageSource: 'http://chromademo.cloudapp.net:81/api/GetOperatorImage',
//setSite: 'http://chromademo.cloudapp.net:81/api/setSite',
//getWindows: 'http://chromademo.cloudapp.net:81/api/getWindows'
});

Some files were not shown because too many files have changed in this diff Show More