diff --git a/api/endpoints/scaffolding.go b/api/endpoints/scaffolding.go index f30a0e5688e49a82831f939f0adc5df1538e4782..14e368cbcd0ab860bc1c5148de63551147162c51 100644 --- a/api/endpoints/scaffolding.go +++ b/api/endpoints/scaffolding.go @@ -80,13 +80,6 @@ func createPart(w http.ResponseWriter, r *http.Request) { return } - //Prints the amount of scaffolding parts added to the system - err = json.NewEncoder(w).Encode(strconv.Itoa(len(scaffoldList)) + " new scaffolding units added to the system the following units were added:") - if err != nil { - tool.HandleError(tool.ENCODINGERROR, w) - return - } - for i := range scaffoldList { //For loop iterates through the list of new scaffolding parts newPartPath := database.Client.Collection(constants.S_TrackingUnitCollection).Doc(constants.S_ScaffoldingParts).Collection(scaffoldList[i].Type).Doc(scaffoldList[i].Id) diff --git a/stillasMobileApplication/stillasMobileApplication.xcodeproj/project.pbxproj b/stillasMobileApplication/stillasMobileApplication.xcodeproj/project.pbxproj index efc207c09a8bd1d45555f7071db8f37ae247f169..75ddafcb518980e4d724651ad89521f47253da04 100644 --- a/stillasMobileApplication/stillasMobileApplication.xcodeproj/project.pbxproj +++ b/stillasMobileApplication/stillasMobileApplication.xcodeproj/project.pbxproj @@ -7,9 +7,53 @@ objects = { /* Begin PBXBuildFile section */ + 2723CF5C27FC6E4500210416 /* TransfereScaffolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723CF5B27FC6E4500210416 /* TransfereScaffolding.swift */; }; + 2723CF8527FECE6E00210416 /* CacheEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723CF8427FECE6E00210416 /* CacheEntry.swift */; }; + 2723CF8827FEF72C00210416 /* CacheImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723CF8727FEF72C00210416 /* CacheImplementation.swift */; }; + 2723CF8A27FEFA1400210416 /* ProjectData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723CF8927FEFA1400210416 /* ProjectData.swift */; }; + 2736C04D27FAD2E90038BD7B /* ProjectInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2736C04C27FAD2E90038BD7B /* ProjectInfoView.swift */; }; + 27373E7B280EAF0100C83150 /* FilterProjectArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27373E7A280EAF0100C83150 /* FilterProjectArea.swift */; }; + 27373E7D280F10F100C83150 /* FilterProjectSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27373E7C280F10F100C83150 /* FilterProjectSize.swift */; }; + 27373E7F281023D900C83150 /* FilterProjectPeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27373E7E281023D900C83150 /* FilterProjectPeriod.swift */; }; 274C541127EE02F2002CE76A /* BlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541027EE02F2002CE76A /* BlurView.swift */; }; - 274C541327EE0316002CE76A /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541227EE0316002CE76A /* MapView.swift */; }; - 274C541527EE0339002CE76A /* CustomCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541427EE0339002CE76A /* CustomCorners.swift */; }; + 274C541827F1B0A5002CE76A /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541727F1B0A5002CE76A /* MapView.swift */; }; + 274C541A27F1B277002CE76A /* NavigationBarBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541927F1B277002CE76A /* NavigationBarBottom.swift */; }; + 274C541D27F1B44C002CE76A /* ScaffoldingTransfere.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C541C27F1B44C002CE76A /* ScaffoldingTransfere.swift */; }; + 274C542427F1D0DC002CE76A /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C542327F1D0DC002CE76A /* ProfileView.swift */; }; + 274C542627F1D133002CE76A /* ProjectListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C542527F1D133002CE76A /* ProjectListView.swift */; }; + 274C542827F1DEC3002CE76A /* CircleImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C542727F1DEC3002CE76A /* CircleImage.swift */; }; + 274C542E27F1F3CD002CE76A /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C542D27F1F3CD002CE76A /* User.swift */; }; + 274C543227F311E6002CE76A /* MapDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C543127F311E6002CE76A /* MapDisplay.swift */; }; + 274C543427F34978002CE76A /* MapViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C543327F34977002CE76A /* MapViewModel.swift */; }; + 274C543927F4889C002CE76A /* Project.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C543827F4889C002CE76A /* Project.swift */; }; + 27D2D6CA282AF0DD0041D014 /* ProfileData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27D2D6C9282AF0DC0041D014 /* ProfileData.swift */; }; + 27D2D6CC282AF1040041D014 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27D2D6CB282AF1040041D014 /* Profile.swift */; }; + 27E980A5281710CC00118DB3 /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980A4281710CC00118DB3 /* FilterView.swift */; }; + 27E980AB2819D4C100118DB3 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980AA2819D4C100118DB3 /* CalendarView.swift */; }; + 27E980AD2819D8DE00118DB3 /* CheckBoxRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980AC2819D8DE00118DB3 /* CheckBoxRow.swift */; }; + 27E980B0281B085100118DB3 /* ActiveSizeFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980AF281B085100118DB3 /* ActiveSizeFilterView.swift */; }; + 27E980B3281B089300118DB3 /* ActiveAreaFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980B2281B089300118DB3 /* ActiveAreaFilterView.swift */; }; + 27E980B5281B08C000118DB3 /* ActivePeriodFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980B4281B08C000118DB3 /* ActivePeriodFilterView.swift */; }; + 27E980B7281B0A1A00118DB3 /* FilterProjectStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980B6281B0A1A00118DB3 /* FilterProjectStatus.swift */; }; + 27E980BB281B26B900118DB3 /* ActiveStatusFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980BA281B26B900118DB3 /* ActiveStatusFilterView.swift */; }; + 27E980BD281B2A6C00118DB3 /* SizeBetweenFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980BC281B2A6C00118DB3 /* SizeBetweenFilter.swift */; }; + 27E980BF281B383200118DB3 /* SizeGreaterThanFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980BE281B383200118DB3 /* SizeGreaterThanFilter.swift */; }; + 27E980C1281B384600118DB3 /* SizeLessThanFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980C0281B384600118DB3 /* SizeLessThanFilter.swift */; }; + 27E980C4281BE49200118DB3 /* CornerRadiusStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980C3281BE49200118DB3 /* CornerRadiusStyle.swift */; }; + 27E980C8281BFA6D00118DB3 /* ScaffoldingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980C7281BFA6D00118DB3 /* ScaffoldingView.swift */; }; + 27E980CA281BFAA700118DB3 /* ProjectInfoDetailedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980C9281BFAA700118DB3 /* ProjectInfoDetailedView.swift */; }; + 27E980CD281E9F5300118DB3 /* TransfereScaffoldingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980CC281E9F5300118DB3 /* TransfereScaffoldingButton.swift */; }; + 27E980CF281E9F9800118DB3 /* ScaffoldingItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980CE281E9F9800118DB3 /* ScaffoldingItems.swift */; }; + 27E980D1281E9FBB00118DB3 /* ScaffoldingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980D0281E9FBB00118DB3 /* ScaffoldingItem.swift */; }; + 27E980D42822EC8100118DB3 /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 27E980D32822EC8100118DB3 /* FirebaseAuth */; }; + 27E980D82822F50B00118DB3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 27E980D72822F50A00118DB3 /* GoogleService-Info.plist */; }; + 27E980DD282409CC00118DB3 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980DC282409CC00118DB3 /* SignInView.swift */; }; + 27E980DF28240A1E00118DB3 /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980DE28240A1E00118DB3 /* SignUpView.swift */; }; + 27E980E128240A5200118DB3 /* AppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980E028240A5200118DB3 /* AppViewModel.swift */; }; + 27E980E42829AE5E00118DB3 /* TransfereScaffoldingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980E32829AE5E00118DB3 /* TransfereScaffoldingView.swift */; }; + 27E980E62829B0E600118DB3 /* HistoryOfScaffolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980E52829B0E600118DB3 /* HistoryOfScaffolding.swift */; }; + 27E980E82829B3A700118DB3 /* ScaffoldingDetailedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980E72829B3A700118DB3 /* ScaffoldingDetailedView.swift */; }; + 27E980EB2829B5F200118DB3 /* TextFieldModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E980EA2829B5F200118DB3 /* TextFieldModifiers.swift */; }; 4D17100627D755400026C216 /* stillasMobileApplicationApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D17100527D755400026C216 /* stillasMobileApplicationApp.swift */; }; 4D17100827D755400026C216 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D17100727D755400026C216 /* ContentView.swift */; }; 4D17100A27D755410026C216 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4D17100927D755410026C216 /* Assets.xcassets */; }; @@ -17,9 +61,53 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 2723CF5B27FC6E4500210416 /* TransfereScaffolding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransfereScaffolding.swift; sourceTree = "<group>"; }; + 2723CF8427FECE6E00210416 /* CacheEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheEntry.swift; sourceTree = "<group>"; }; + 2723CF8727FEF72C00210416 /* CacheImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheImplementation.swift; sourceTree = "<group>"; }; + 2723CF8927FEFA1400210416 /* ProjectData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectData.swift; sourceTree = "<group>"; }; + 2736C04C27FAD2E90038BD7B /* ProjectInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectInfoView.swift; sourceTree = "<group>"; }; + 27373E7A280EAF0100C83150 /* FilterProjectArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterProjectArea.swift; sourceTree = "<group>"; }; + 27373E7C280F10F100C83150 /* FilterProjectSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterProjectSize.swift; sourceTree = "<group>"; }; + 27373E7E281023D900C83150 /* FilterProjectPeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterProjectPeriod.swift; sourceTree = "<group>"; }; 274C541027EE02F2002CE76A /* BlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurView.swift; sourceTree = "<group>"; }; - 274C541227EE0316002CE76A /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; }; - 274C541427EE0339002CE76A /* CustomCorners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCorners.swift; sourceTree = "<group>"; }; + 274C541727F1B0A5002CE76A /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; }; + 274C541927F1B277002CE76A /* NavigationBarBottom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarBottom.swift; sourceTree = "<group>"; }; + 274C541C27F1B44C002CE76A /* ScaffoldingTransfere.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaffoldingTransfere.swift; sourceTree = "<group>"; }; + 274C542327F1D0DC002CE76A /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; }; + 274C542527F1D133002CE76A /* ProjectListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectListView.swift; sourceTree = "<group>"; }; + 274C542727F1DEC3002CE76A /* CircleImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleImage.swift; sourceTree = "<group>"; }; + 274C542D27F1F3CD002CE76A /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; }; + 274C543127F311E6002CE76A /* MapDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDisplay.swift; sourceTree = "<group>"; }; + 274C543327F34977002CE76A /* MapViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewModel.swift; sourceTree = "<group>"; }; + 274C543527F44104002CE76A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; + 274C543827F4889C002CE76A /* Project.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Project.swift; sourceTree = "<group>"; }; + 27D2D6C9282AF0DC0041D014 /* ProfileData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileData.swift; sourceTree = "<group>"; }; + 27D2D6CB282AF1040041D014 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = "<group>"; }; + 27E980A4281710CC00118DB3 /* FilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterView.swift; sourceTree = "<group>"; }; + 27E980AA2819D4C100118DB3 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; }; + 27E980AC2819D8DE00118DB3 /* CheckBoxRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBoxRow.swift; sourceTree = "<group>"; }; + 27E980AF281B085100118DB3 /* ActiveSizeFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSizeFilterView.swift; sourceTree = "<group>"; }; + 27E980B2281B089300118DB3 /* ActiveAreaFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveAreaFilterView.swift; sourceTree = "<group>"; }; + 27E980B4281B08C000118DB3 /* ActivePeriodFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePeriodFilterView.swift; sourceTree = "<group>"; }; + 27E980B6281B0A1A00118DB3 /* FilterProjectStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterProjectStatus.swift; sourceTree = "<group>"; }; + 27E980BA281B26B900118DB3 /* ActiveStatusFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveStatusFilterView.swift; sourceTree = "<group>"; }; + 27E980BC281B2A6C00118DB3 /* SizeBetweenFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeBetweenFilter.swift; sourceTree = "<group>"; }; + 27E980BE281B383200118DB3 /* SizeGreaterThanFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeGreaterThanFilter.swift; sourceTree = "<group>"; }; + 27E980C0281B384600118DB3 /* SizeLessThanFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeLessThanFilter.swift; sourceTree = "<group>"; }; + 27E980C3281BE49200118DB3 /* CornerRadiusStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadiusStyle.swift; sourceTree = "<group>"; }; + 27E980C7281BFA6D00118DB3 /* ScaffoldingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaffoldingView.swift; sourceTree = "<group>"; }; + 27E980C9281BFAA700118DB3 /* ProjectInfoDetailedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectInfoDetailedView.swift; sourceTree = "<group>"; }; + 27E980CC281E9F5300118DB3 /* TransfereScaffoldingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransfereScaffoldingButton.swift; sourceTree = "<group>"; }; + 27E980CE281E9F9800118DB3 /* ScaffoldingItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaffoldingItems.swift; sourceTree = "<group>"; }; + 27E980D0281E9FBB00118DB3 /* ScaffoldingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaffoldingItem.swift; sourceTree = "<group>"; }; + 27E980D72822F50A00118DB3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; + 27E980DC282409CC00118DB3 /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = "<group>"; }; + 27E980DE28240A1E00118DB3 /* SignUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpView.swift; sourceTree = "<group>"; }; + 27E980E028240A5200118DB3 /* AppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewModel.swift; sourceTree = "<group>"; }; + 27E980E32829AE5E00118DB3 /* TransfereScaffoldingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransfereScaffoldingView.swift; sourceTree = "<group>"; }; + 27E980E52829B0E600118DB3 /* HistoryOfScaffolding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryOfScaffolding.swift; sourceTree = "<group>"; }; + 27E980E72829B3A700118DB3 /* ScaffoldingDetailedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaffoldingDetailedView.swift; sourceTree = "<group>"; }; + 27E980EA2829B5F200118DB3 /* TextFieldModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldModifiers.swift; sourceTree = "<group>"; }; 4D17100227D755400026C216 /* stillasMobileApplication.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = stillasMobileApplication.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4D17100527D755400026C216 /* stillasMobileApplicationApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = stillasMobileApplicationApp.swift; sourceTree = "<group>"; }; 4D17100727D755400026C216 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; @@ -32,12 +120,43 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 27E980D42822EC8100118DB3 /* FirebaseAuth in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2723CF7E27FD9EA800210416 /* FetchingApiData */ = { + isa = PBXGroup; + children = ( + 2723CF8927FEFA1400210416 /* ProjectData.swift */, + 27D2D6C9282AF0DC0041D014 /* ProfileData.swift */, + ); + path = FetchingApiData; + sourceTree = "<group>"; + }; + 2723CF8627FEF60C00210416 /* Cache */ = { + isa = PBXGroup; + children = ( + 2723CF8427FECE6E00210416 /* CacheEntry.swift */, + 2723CF8727FEF72C00210416 /* CacheImplementation.swift */, + ); + path = Cache; + sourceTree = "<group>"; + }; + 27373E79280EAEBF00C83150 /* FilterData */ = { + isa = PBXGroup; + children = ( + 27E980AE281B083800118DB3 /* FilterView */, + 27E980A928193A8500118DB3 /* StatusFilter */, + 27E980A828193A7D00118DB3 /* PeriodFilter */, + 27E980A728193A7300118DB3 /* SizeFilter */, + 27E980A628193A6200118DB3 /* AreaFilter */, + ); + path = FilterData; + sourceTree = "<group>"; + }; 274C540E27EE02C4002CE76A /* BlurView */ = { isa = PBXGroup; children = ( @@ -46,24 +165,211 @@ path = BlurView; sourceTree = "<group>"; }; - 274C540F27EE02DD002CE76A /* MapView */ = { + 274C541627F1B097002CE76A /* MapView */ = { isa = PBXGroup; children = ( - 274C541227EE0316002CE76A /* MapView.swift */, + 274C541727F1B0A5002CE76A /* MapView.swift */, + 274C543127F311E6002CE76A /* MapDisplay.swift */, + 274C543327F34977002CE76A /* MapViewModel.swift */, ); path = MapView; sourceTree = "<group>"; }; + 274C541E27F1B4BB002CE76A /* Model */ = { + isa = PBXGroup; + children = ( + 2723CF7E27FD9EA800210416 /* FetchingApiData */, + 274C541C27F1B44C002CE76A /* ScaffoldingTransfere.swift */, + 274C542D27F1F3CD002CE76A /* User.swift */, + 274C543827F4889C002CE76A /* Project.swift */, + 27D2D6CB282AF1040041D014 /* Profile.swift */, + ); + path = Model; + sourceTree = "<group>"; + }; + 274C542927F1DEFC002CE76A /* Helpers */ = { + isa = PBXGroup; + children = ( + 274C542727F1DEC3002CE76A /* CircleImage.swift */, + 27E980AC2819D8DE00118DB3 /* CheckBoxRow.swift */, + 27E980C3281BE49200118DB3 /* CornerRadiusStyle.swift */, + 27E980EA2829B5F200118DB3 /* TextFieldModifiers.swift */, + ); + path = Helpers; + sourceTree = "<group>"; + }; + 274C542F27F2E03A002CE76A /* ProfileView */ = { + isa = PBXGroup; + children = ( + 274C542327F1D0DC002CE76A /* ProfileView.swift */, + ); + path = ProfileView; + sourceTree = "<group>"; + }; + 274C543027F2E049002CE76A /* ProjectView */ = { + isa = PBXGroup; + children = ( + 27373E79280EAEBF00C83150 /* FilterData */, + 27E980C5281BF9E100118DB3 /* ProjectView */, + ); + path = ProjectView; + sourceTree = "<group>"; + }; 27E5158527EE00D900EFE9A1 /* Views */ = { isa = PBXGroup; children = ( - 274C540F27EE02DD002CE76A /* MapView */, + 2723CF8627FEF60C00210416 /* Cache */, + 274C543027F2E049002CE76A /* ProjectView */, + 274C542F27F2E03A002CE76A /* ProfileView */, + 274C541E27F1B4BB002CE76A /* Model */, + 274C541627F1B097002CE76A /* MapView */, 274C540E27EE02C4002CE76A /* BlurView */, - 274C541427EE0339002CE76A /* CustomCorners.swift */, + 274C541927F1B277002CE76A /* NavigationBarBottom.swift */, + 274C542927F1DEFC002CE76A /* Helpers */, ); path = Views; sourceTree = "<group>"; }; + 27E980A628193A6200118DB3 /* AreaFilter */ = { + isa = PBXGroup; + children = ( + 27373E7A280EAF0100C83150 /* FilterProjectArea.swift */, + ); + path = AreaFilter; + sourceTree = "<group>"; + }; + 27E980A728193A7300118DB3 /* SizeFilter */ = { + isa = PBXGroup; + children = ( + 27E980C2281B386200118DB3 /* BetweenGreaterLess */, + 27373E7C280F10F100C83150 /* FilterProjectSize.swift */, + ); + path = SizeFilter; + sourceTree = "<group>"; + }; + 27E980A828193A7D00118DB3 /* PeriodFilter */ = { + isa = PBXGroup; + children = ( + 27373E7E281023D900C83150 /* FilterProjectPeriod.swift */, + 27E980AA2819D4C100118DB3 /* CalendarView.swift */, + ); + path = PeriodFilter; + sourceTree = "<group>"; + }; + 27E980A928193A8500118DB3 /* StatusFilter */ = { + isa = PBXGroup; + children = ( + 27E980B6281B0A1A00118DB3 /* FilterProjectStatus.swift */, + ); + path = StatusFilter; + sourceTree = "<group>"; + }; + 27E980AE281B083800118DB3 /* FilterView */ = { + isa = PBXGroup; + children = ( + 27E980B1281B087100118DB3 /* ActiveFilterViews */, + 27E980A4281710CC00118DB3 /* FilterView.swift */, + ); + path = FilterView; + sourceTree = "<group>"; + }; + 27E980B1281B087100118DB3 /* ActiveFilterViews */ = { + isa = PBXGroup; + children = ( + 27E980AF281B085100118DB3 /* ActiveSizeFilterView.swift */, + 27E980B2281B089300118DB3 /* ActiveAreaFilterView.swift */, + 27E980B4281B08C000118DB3 /* ActivePeriodFilterView.swift */, + 27E980BA281B26B900118DB3 /* ActiveStatusFilterView.swift */, + ); + path = ActiveFilterViews; + sourceTree = "<group>"; + }; + 27E980C2281B386200118DB3 /* BetweenGreaterLess */ = { + isa = PBXGroup; + children = ( + 27E980BC281B2A6C00118DB3 /* SizeBetweenFilter.swift */, + 27E980BE281B383200118DB3 /* SizeGreaterThanFilter.swift */, + 27E980C0281B384600118DB3 /* SizeLessThanFilter.swift */, + ); + path = BetweenGreaterLess; + sourceTree = "<group>"; + }; + 27E980C5281BF9E100118DB3 /* ProjectView */ = { + isa = PBXGroup; + children = ( + 274C542527F1D133002CE76A /* ProjectListView.swift */, + 27E980C6281BF9FE00118DB3 /* ProjectDetailView */, + ); + path = ProjectView; + sourceTree = "<group>"; + }; + 27E980C6281BF9FE00118DB3 /* ProjectDetailView */ = { + isa = PBXGroup; + children = ( + 27E980CB281E9F3900118DB3 /* ScaffoldingView */, + 2736C04C27FAD2E90038BD7B /* ProjectInfoView.swift */, + 27E980C9281BFAA700118DB3 /* ProjectInfoDetailedView.swift */, + ); + path = ProjectDetailView; + sourceTree = "<group>"; + }; + 27E980CB281E9F3900118DB3 /* ScaffoldingView */ = { + isa = PBXGroup; + children = ( + 27E980C7281BFA6D00118DB3 /* ScaffoldingView.swift */, + 27E980CE281E9F9800118DB3 /* ScaffoldingItems.swift */, + 27E980D0281E9FBB00118DB3 /* ScaffoldingItem.swift */, + 27E980E92829B49B00118DB3 /* ScaffoldingHistory */, + 27E980E22829A6E600118DB3 /* TransfereScaffolding */, + ); + name = ScaffoldingView; + sourceTree = "<group>"; + }; + 27E980D92824099100118DB3 /* Login&Signup */ = { + isa = PBXGroup; + children = ( + 27E980DB282409BB00118DB3 /* SignUp */, + 27E980DA282409B600118DB3 /* Login */, + 27E980E028240A5200118DB3 /* AppViewModel.swift */, + ); + path = "Login&Signup"; + sourceTree = "<group>"; + }; + 27E980DA282409B600118DB3 /* Login */ = { + isa = PBXGroup; + children = ( + 27E980DC282409CC00118DB3 /* SignInView.swift */, + ); + path = Login; + sourceTree = "<group>"; + }; + 27E980DB282409BB00118DB3 /* SignUp */ = { + isa = PBXGroup; + children = ( + 27E980DE28240A1E00118DB3 /* SignUpView.swift */, + ); + path = SignUp; + sourceTree = "<group>"; + }; + 27E980E22829A6E600118DB3 /* TransfereScaffolding */ = { + isa = PBXGroup; + children = ( + 2723CF5B27FC6E4500210416 /* TransfereScaffolding.swift */, + 27E980E32829AE5E00118DB3 /* TransfereScaffoldingView.swift */, + ); + name = TransfereScaffolding; + sourceTree = "<group>"; + }; + 27E980E92829B49B00118DB3 /* ScaffoldingHistory */ = { + isa = PBXGroup; + children = ( + 27E980E72829B3A700118DB3 /* ScaffoldingDetailedView.swift */, + 27E980CC281E9F5300118DB3 /* TransfereScaffoldingButton.swift */, + 27E980E52829B0E600118DB3 /* HistoryOfScaffolding.swift */, + ); + name = ScaffoldingHistory; + sourceTree = "<group>"; + }; 4D170FF927D755400026C216 = { isa = PBXGroup; children = ( @@ -83,7 +389,10 @@ 4D17100427D755400026C216 /* stillasMobileApplication */ = { isa = PBXGroup; children = ( + 27E980D72822F50A00118DB3 /* GoogleService-Info.plist */, + 274C543527F44104002CE76A /* Info.plist */, 27E5158527EE00D900EFE9A1 /* Views */, + 27E980D92824099100118DB3 /* Login&Signup */, 4D17100527D755400026C216 /* stillasMobileApplicationApp.swift */, 4D17100727D755400026C216 /* ContentView.swift */, 4D17100927D755410026C216 /* Assets.xcassets */, @@ -116,6 +425,9 @@ dependencies = ( ); name = stillasMobileApplication; + packageProductDependencies = ( + 27E980D32822EC8100118DB3 /* FirebaseAuth */, + ); productName = stillasMobileApplication; productReference = 4D17100227D755400026C216 /* stillasMobileApplication.app */; productType = "com.apple.product-type.application"; @@ -144,6 +456,9 @@ Base, ); mainGroup = 4D170FF927D755400026C216; + packageReferences = ( + 27E980D22822EC8100118DB3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + ); productRefGroup = 4D17100327D755400026C216 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -159,6 +474,7 @@ buildActionMask = 2147483647; files = ( 4D17100D27D755410026C216 /* Preview Assets.xcassets in Resources */, + 27E980D82822F50B00118DB3 /* GoogleService-Info.plist in Resources */, 4D17100A27D755410026C216 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -170,11 +486,53 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 27E980A5281710CC00118DB3 /* FilterView.swift in Sources */, 4D17100827D755400026C216 /* ContentView.swift in Sources */, - 274C541327EE0316002CE76A /* MapView.swift in Sources */, + 27E980BD281B2A6C00118DB3 /* SizeBetweenFilter.swift in Sources */, + 274C541A27F1B277002CE76A /* NavigationBarBottom.swift in Sources */, + 27373E7B280EAF0100C83150 /* FilterProjectArea.swift in Sources */, + 27E980E82829B3A700118DB3 /* ScaffoldingDetailedView.swift in Sources */, + 2723CF5C27FC6E4500210416 /* TransfereScaffolding.swift in Sources */, + 27E980B7281B0A1A00118DB3 /* FilterProjectStatus.swift in Sources */, + 274C541827F1B0A5002CE76A /* MapView.swift in Sources */, + 27E980B5281B08C000118DB3 /* ActivePeriodFilterView.swift in Sources */, + 27E980E62829B0E600118DB3 /* HistoryOfScaffolding.swift in Sources */, + 27E980E128240A5200118DB3 /* AppViewModel.swift in Sources */, + 27E980DD282409CC00118DB3 /* SignInView.swift in Sources */, + 274C542827F1DEC3002CE76A /* CircleImage.swift in Sources */, + 27E980AB2819D4C100118DB3 /* CalendarView.swift in Sources */, + 27E980E42829AE5E00118DB3 /* TransfereScaffoldingView.swift in Sources */, + 27E980C1281B384600118DB3 /* SizeLessThanFilter.swift in Sources */, + 27373E7F281023D900C83150 /* FilterProjectPeriod.swift in Sources */, + 27E980B3281B089300118DB3 /* ActiveAreaFilterView.swift in Sources */, + 274C543227F311E6002CE76A /* MapDisplay.swift in Sources */, + 274C543427F34978002CE76A /* MapViewModel.swift in Sources */, + 2723CF8A27FEFA1400210416 /* ProjectData.swift in Sources */, + 274C542E27F1F3CD002CE76A /* User.swift in Sources */, + 27E980CA281BFAA700118DB3 /* ProjectInfoDetailedView.swift in Sources */, + 27E980B0281B085100118DB3 /* ActiveSizeFilterView.swift in Sources */, + 27E980D1281E9FBB00118DB3 /* ScaffoldingItem.swift in Sources */, + 27E980CF281E9F9800118DB3 /* ScaffoldingItems.swift in Sources */, + 27D2D6CC282AF1040041D014 /* Profile.swift in Sources */, + 27E980BF281B383200118DB3 /* SizeGreaterThanFilter.swift in Sources */, + 27E980DF28240A1E00118DB3 /* SignUpView.swift in Sources */, + 274C541D27F1B44C002CE76A /* ScaffoldingTransfere.swift in Sources */, + 27E980AD2819D8DE00118DB3 /* CheckBoxRow.swift in Sources */, + 27E980C4281BE49200118DB3 /* CornerRadiusStyle.swift in Sources */, + 274C542627F1D133002CE76A /* ProjectListView.swift in Sources */, + 2723CF8527FECE6E00210416 /* CacheEntry.swift in Sources */, + 2723CF8827FEF72C00210416 /* CacheImplementation.swift in Sources */, + 27E980EB2829B5F200118DB3 /* TextFieldModifiers.swift in Sources */, + 2736C04D27FAD2E90038BD7B /* ProjectInfoView.swift in Sources */, 4D17100627D755400026C216 /* stillasMobileApplicationApp.swift in Sources */, + 27373E7D280F10F100C83150 /* FilterProjectSize.swift in Sources */, 274C541127EE02F2002CE76A /* BlurView.swift in Sources */, - 274C541527EE0339002CE76A /* CustomCorners.swift in Sources */, + 27E980CD281E9F5300118DB3 /* TransfereScaffoldingButton.swift in Sources */, + 27E980C8281BFA6D00118DB3 /* ScaffoldingView.swift in Sources */, + 27D2D6CA282AF0DD0041D014 /* ProfileData.swift in Sources */, + 27E980BB281B26B900118DB3 /* ActiveStatusFilterView.swift in Sources */, + 274C543927F4889C002CE76A /* Project.swift in Sources */, + 274C542427F1D0DC002CE76A /* ProfileView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -232,7 +590,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -287,7 +645,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -305,20 +663,23 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"stillasMobileApplication/Preview Content\""; - DEVELOPMENT_TEAM = J3YJ2A9XWC; + DEVELOPMENT_TEAM = 9M752RTPAF; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = stillasMobileApplication/Info.plist; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "We use your location to show you where you are located on the map"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.-stillasMobileApplication"; + PRODUCT_BUNDLE_IDENTIFIER = bachelor.stillasMobileApplication; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -334,14 +695,17 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"stillasMobileApplication/Preview Content\""; - DEVELOPMENT_TEAM = J3YJ2A9XWC; + DEVELOPMENT_TEAM = 9M752RTPAF; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = stillasMobileApplication/Info.plist; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "We use your location to show you where you are located on the map"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -377,6 +741,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 27E980D22822EC8100118DB3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 27E980D32822EC8100118DB3 /* FirebaseAuth */ = { + isa = XCSwiftPackageProductDependency; + package = 27E980D22822EC8100118DB3 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAuth; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 4D170FFA27D755400026C216 /* Project object */; } diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Profile/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Profile/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..73c00596a7fca3f3d4bdd64053b69d86745f9e10 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Profile/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/UserProfile.imageset/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Profile/UserProfile.imageset/Contents.json similarity index 100% rename from stillasMobileApplication/stillasMobileApplication/Assets.xcassets/UserProfile.imageset/Contents.json rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Profile/UserProfile.imageset/Contents.json diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/UserProfile.imageset/clipart2557794.png b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Profile/UserProfile.imageset/clipart2557794.png similarity index 100% rename from stillasMobileApplication/stillasMobileApplication/Assets.xcassets/UserProfile.imageset/clipart2557794.png rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Profile/UserProfile.imageset/clipart2557794.png diff --git a/webstillas/src/components/scaffolding/images/Bunnskrue.jpg b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Bunnskrue.imageset/Bunnskrue.jpeg similarity index 100% rename from webstillas/src/components/scaffolding/images/Bunnskrue.jpg rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Bunnskrue.imageset/Bunnskrue.jpeg diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Bunnskrue.imageset/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Bunnskrue.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..339ea99989066ccd695d69377dc3b128503491ea --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Bunnskrue.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Bunnskrue.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..73c00596a7fca3f3d4bdd64053b69d86745f9e10 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Diagonalstang.imageset/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Diagonalstang.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..d34de197449b58c42290dd756599a45997aa6115 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Diagonalstang.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Diagonalstag.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/webstillas/src/components/scaffolding/images/Diagonalstang.jpg b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Diagonalstang.imageset/Diagonalstag.jpeg similarity index 100% rename from webstillas/src/components/scaffolding/images/Diagonalstang.jpg rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Diagonalstang.imageset/Diagonalstag.jpeg diff --git "a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Enr\303\270rsbjelke.imageset/Contents.json" "b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Enr\303\270rsbjelke.imageset/Contents.json" new file mode 100644 index 0000000000000000000000000000000000000000..d67644864c9b9d2f9244bd140e03f03618fc1fac --- /dev/null +++ "b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Enr\303\270rsbjelke.imageset/Contents.json" @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Enrørsbjelke.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git "a/webstillas/src/components/scaffolding/images/Enr\303\270rsbjelke.jpg" "b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Enr\303\270rsbjelke.imageset/Enr\303\270rsbjelke.jpeg" similarity index 100% rename from "webstillas/src/components/scaffolding/images/Enr\303\270rsbjelke.jpg" rename to "stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Enr\303\270rsbjelke.imageset/Enr\303\270rsbjelke.jpeg" diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Gelender.imageset/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Gelender.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..0317b454f4bd5d504d6604749af1ca1a0c4baf8c --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Gelender.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gelender.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/webstillas/src/components/scaffolding/images/Gelender.jpg b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Gelender.imageset/Gelender.jpeg similarity index 100% rename from webstillas/src/components/scaffolding/images/Gelender.jpg rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Gelender.imageset/Gelender.jpeg diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Lengdebjelke.imageset/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Lengdebjelke.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..1e0c9a00b1482bc05fb3308d9071bfcb0e027d9a --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Lengdebjelke.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Lengdebjelke.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/webstillas/src/components/scaffolding/images/Lengdebjelke.jpg b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Lengdebjelke.imageset/Lengdebjelke.jpeg similarity index 100% rename from webstillas/src/components/scaffolding/images/Lengdebjelke.jpg rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Lengdebjelke.imageset/Lengdebjelke.jpeg diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Plank.imageset/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Plank.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..fe9fa12398b9939926ebfefa19823f96dc2bba56 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Plank.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Plank.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/webstillas/src/components/scaffolding/images/Plank.jpg b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Plank.imageset/Plank.jpeg similarity index 100% rename from webstillas/src/components/scaffolding/images/Plank.jpg rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Plank.imageset/Plank.jpeg diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Rekkverksramme.imageset/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Rekkverksramme.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..b2bdcbb6fc5b23134b9fdb1a69a9c0f230f55932 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Rekkverksramme.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Rekkverk.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/webstillas/src/components/scaffolding/images/Rekkverksramme.jpg b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Rekkverksramme.imageset/Rekkverk.jpeg similarity index 100% rename from webstillas/src/components/scaffolding/images/Rekkverksramme.jpg rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Rekkverksramme.imageset/Rekkverk.jpeg diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Spir.imageset/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Spir.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..08ac02868fb8f5f55ec78e592bed3969feb63b80 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Spir.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Spir.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/webstillas/src/components/scaffolding/images/Spir.jpg b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Spir.imageset/Spir.png similarity index 100% rename from webstillas/src/components/scaffolding/images/Spir.jpg rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Spir.imageset/Spir.png diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Stillaslem.imageset/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Stillaslem.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..ff8f716337ba742e3393e5e44cb9882933814384 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Stillaslem.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Stillaslem.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/webstillas/src/components/scaffolding/images/Stillaslem.jpg b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Stillaslem.imageset/Stillaslem.jpeg similarity index 100% rename from webstillas/src/components/scaffolding/images/Stillaslem.jpg rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Stillaslem.imageset/Stillaslem.jpeg diff --git a/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Trapp.imageset/Contents.json b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Trapp.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..d072ea4527adb91e3b9bafe2153c29099301c93b --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Trapp.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Trapp.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/webstillas/src/components/scaffolding/images/Trapp.jpg b/stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Trapp.imageset/Trapp.jpeg similarity index 100% rename from webstillas/src/components/scaffolding/images/Trapp.jpg rename to stillasMobileApplication/stillasMobileApplication/Assets.xcassets/Scaffolding/Trapp.imageset/Trapp.jpeg diff --git a/stillasMobileApplication/stillasMobileApplication/ContentView.swift b/stillasMobileApplication/stillasMobileApplication/ContentView.swift index 182e43c9701d0d341f2279f0b5d0499f29128f58..b4071db45969dc1e29107d7a3231630f933e47e1 100644 --- a/stillasMobileApplication/stillasMobileApplication/ContentView.swift +++ b/stillasMobileApplication/stillasMobileApplication/ContentView.swift @@ -7,13 +7,31 @@ import SwiftUI -/** - ContentView is responsible for the views in the application. - This will need enum and TabView on a later stage to switch between views. - */ +/// **ContentView** +/// Responsible for the views in the application. +/// This will need enum and TabView on a later stage to switch between views. +/// https://www.youtube.com/watch?v=vPCEIPL0U_k +/// https://firebase.google.com/docs/auth/ios/start struct ContentView: View { + @State var email = "" + @State var password = "" + + /// The model responsible for sign in and sign up + @EnvironmentObject var viewModel: AppViewModel + var body: some View { - NavigationBarBottom() + ZStack { + /// If user is signed in, give the user access to the application, if not prompt the user with the login and sign up view + if viewModel.signedIn { + NavigationBarBottom() + } else { + SignInView() + } + } + .onAppear { + /// Remembers if the user was signed in and closes the application + viewModel.signedIn = viewModel.isSignedIn + } } } diff --git a/stillasMobileApplication/stillasMobileApplication/GoogleService-Info.plist b/stillasMobileApplication/stillasMobileApplication/GoogleService-Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..5f32f9c1354c025d4769a34a03949508227bb06e --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/GoogleService-Info.plist @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CLIENT_ID</key> + <string>586975019426-gv1n44k324m6vvrujrskqtak9jt4k19n.apps.googleusercontent.com</string> + <key>REVERSED_CLIENT_ID</key> + <string>com.googleusercontent.apps.586975019426-gv1n44k324m6vvrujrskqtak9jt4k19n</string> + <key>API_KEY</key> + <string>AIzaSyAzuuzPV27C8W4xAOuMLZbWsBy9m7juKgc</string> + <key>GCM_SENDER_ID</key> + <string>586975019426</string> + <key>PLIST_VERSION</key> + <string>1</string> + <key>BUNDLE_ID</key> + <string>bachelor.stillasMobileApplication</string> + <key>PROJECT_ID</key> + <string>stillas-16563</string> + <key>STORAGE_BUCKET</key> + <string>stillas-16563.appspot.com</string> + <key>IS_ADS_ENABLED</key> + <false></false> + <key>IS_ANALYTICS_ENABLED</key> + <false></false> + <key>IS_APPINVITE_ENABLED</key> + <true></true> + <key>IS_GCM_ENABLED</key> + <true></true> + <key>IS_SIGNIN_ENABLED</key> + <true></true> + <key>GOOGLE_APP_ID</key> + <string>1:586975019426:ios:b582fd2bfebd7aa3bc32fb</string> +</dict> +</plist> \ No newline at end of file diff --git a/stillasMobileApplication/stillasMobileApplication/Login&Signup/AppViewModel.swift b/stillasMobileApplication/stillasMobileApplication/Login&Signup/AppViewModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..635dd004875d9a8ca22907f334318969f097cd80 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Login&Signup/AppViewModel.swift @@ -0,0 +1,77 @@ +// +// AppViewModel.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 05/05/2022. +// + +import FirebaseAuth + +/// **AppViewModel** +/// The prosessing class responsible for login and sign up through Firebase Authentication +/// Code taken from and inspired from the following sources: +/// https://www.youtube.com/watch?v=vPCEIPL0U_k +/// https://firebase.google.com/docs/auth/ios/start +class AppViewModel: ObservableObject { + + let auth = Auth.auth() + + /// Is user signed in? + @Published var signedIn: Bool = false + + /// Getter and Setter for the userID + var userID: String { + get { return auth.currentUser?.uid ?? "" } + set { self.userID = newValue } + } + + /// Getter for is user signed in? + var isSignedIn: Bool { + return auth.currentUser != nil + } + + + /// Attempts to sign in user to the application by authorizing the user through the Firebase Authentication + /// - Parameters: + /// - email: The email of the user + /// - password: The password of the user + func signIn(email: String, password: String) { + + /// Attempts to authorize the credentials in Firebase Authentication + auth.signIn(withEmail: email, password: password) { [weak self ] (result, error) in + guard result != nil, error == nil else { + return + } + DispatchQueue.main.async { + // Success + self?.signedIn = true + } + } + } + + + /// Attempts to register a new user to the application by adding them to the Firebase Authentication + /// - Parameters: + /// - email: The email of the new user + /// - password: The password of the new user + func signUp(email: String, password: String) { + /// Attempts to create a new user + auth.createUser(withEmail: email, password: password) { [weak self ] (result, error) in + guard result != nil, error == nil else { + return + } + DispatchQueue.main.async { + // Success + self?.signedIn = true + } + } + } + + + /// Signs the user out of the application + func signOut() { + try? auth.signOut() + + self.signedIn = false + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Login&Signup/Login/SignInView.swift b/stillasMobileApplication/stillasMobileApplication/Login&Signup/Login/SignInView.swift new file mode 100644 index 0000000000000000000000000000000000000000..a6a9e1e314b85373cf1b8579263c2c12fbc05b57 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Login&Signup/Login/SignInView.swift @@ -0,0 +1,102 @@ +// +// SignInView.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 05/05/2022. +// + +import SwiftUI +import MapKit + +/// **SignInView** +/// The view responsible for the sign in for users +/// Code are inspired from: +/// https://medium.com/@success.anil.kk/login-screen-demo-with-swiftui-2f711e0c657d +/// https://www.youtube.com/watch?v=vPCEIPL0U_k +/// https://firebase.google.com/docs/auth/ios/start +struct SignInView: View { + @State var email = "" + @State var password = "" + + /// The model responsible for sign in + @EnvironmentObject var viewModel: AppViewModel + + let verticalPaddingForForm = 40.0 + + var body: some View { + NavigationView{ + ZStack { + /// Darkened background of the login View with the map in the shadows + ZStack { + /// Blur effect + Color.black.frame(width: UIScreen.screenWidth, height: UIScreen.screenHeight, alignment: .center).zIndex(1).opacity(0.5).ignoresSafeArea() + MapView() + .blur(radius: 10) + .allowsHitTesting(false) + } + + VStack(spacing: CGFloat(verticalPaddingForForm)) { + Text("Welcome To MBStillas ST") + .font(.title) + .bold() + HStack { + Image(systemName: "person") + .foregroundColor(.secondary) + + /// The login email + TextField("Enter your email", text: $email) + .foregroundColor(Color.black) + .autocapitalization(.none) + .disableAutocorrection(true) + } + .padding() + .background(Color.white) + .cornerRadius(10) + + HStack { + Image(systemName: "lock") + .foregroundColor(.secondary) + + /// The login password masked + SecureField("Enter password", text: $password) + .foregroundColor(Color.black) + .autocapitalization(.none) + .disableAutocorrection(true) + } + .padding() + .background(Color.white) + .cornerRadius(10) + + /// Sends the login request given that both email and password are filled out + Button(action: { + guard !email.isEmpty, !password.isEmpty else { + return + } + viewModel.signIn(email: email, password: password) + }) { + Text("Sign in") + .frame(width: 150, height: 50, alignment: .center) + } + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(10) + + /// Button to redirect to Sign up + NavigationLink("Sign up", destination: SignUpView()) + .foregroundColor(.white) + .padding() + .background(Color.black.opacity(0.2)) + .cornerRadius(10) + + } + .padding(.horizontal, CGFloat(verticalPaddingForForm)) + } + } + } +} + +struct SignInView_Previews: PreviewProvider { + static var previews: some View { + SignInView() + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Login&Signup/SignUp/SignUpView.swift b/stillasMobileApplication/stillasMobileApplication/Login&Signup/SignUp/SignUpView.swift new file mode 100644 index 0000000000000000000000000000000000000000..67b02d12822cef0b5b0533d10fd54fd6dd280407 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Login&Signup/SignUp/SignUpView.swift @@ -0,0 +1,53 @@ +// +// SignUpView.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 05/05/2022. +// + +import SwiftUI +import FirebaseAuth + +/// **SignUpView** +/// The view responsible for the sign up page +/// Code taken and inspired from this youtube video and firebases authentication startup +/// https://www.youtube.com/watch?v=vPCEIPL0U_k +/// https://firebase.google.com/docs/auth/ios/start +// TODO: Add body where user inputs data in addition to username and password +struct SignUpView: View { + @State var email = "" + @State var password = "" + + /// The model responsible for sign up + @EnvironmentObject var viewModel: AppViewModel + + var body: some View { + VStack { + TextField("Email", text: $email) + .autocapitalization(.none) + .disableAutocorrection(true) + + /// SecureField to mask the password + SecureField("Password", text: $password) + .autocapitalization(.none) + .disableAutocorrection(true) + + /// Sends the request to sign up to the system + Button(action: { + guard !email.isEmpty, !password.isEmpty else { + return + } + viewModel.signUp(email: email, password: password) + }) { + Text("Sign up") + } + } + .padding() + } +} + +struct SignUpView_Previews: PreviewProvider { + static var previews: some View { + SignUpView() + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Resources/projectData.json b/stillasMobileApplication/stillasMobileApplication/Resources/projectData.json deleted file mode 100644 index e308079b474cae71847da84cbfa9731e780a6d4d..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Resources/projectData.json +++ /dev/null @@ -1,60 +0,0 @@ -[ - { - "projectID": 420, - "projectName": "Ntnu i gjøvik", - "latitude": 60.7905060889568, - "longitude": 10.681777071532371, - "period": { - "startDate": "20.02.2020", - "endDate": "10.05.2020" - }, - "size": 240, - "State": "Active", - "adresse":{ - "gate": "Piazza del Colosseo 1", - "postnummer": "0184", - "kommune": "Roma", - "fylke": "Roma" - }, - "Leier": { - "name": "Rome", - "number": 639967700 - }, - "Scaffolding": { - "units":[ - { - "type": "Spire", - "quantity": { - "expected": 3241, - "registered":3241 - } - }, - { - "type": "Flooring", - "quantity": { - "expected": 500000, - "registered":499211 - } - } - ] - }, - "geofence": { - "w-position": { - "latitude": 60.79077759591496, - "longitude": 10.683249543160402 - }, - "x-position": { - "latitude": 60.79015256651516, - "longitude": 10.684424851812308 - }, - "y-position":{ - "latitude": 60.789159847696716, - "longitude": 10.68094413003551 - }, - "z-position":{ - "latitude": 60.78963782726421, - "longitude": 10.680160590934236 - } - } - } -] diff --git a/stillasMobileApplication/stillasMobileApplication/Resources/userData.json b/stillasMobileApplication/stillasMobileApplication/Resources/userData.json deleted file mode 100644 index 90efc398979b96b4f93f615e517480326ca30e5b..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Resources/userData.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "id": 232, - "name": "Ola Nordmann", - "dateOfBirth": "01.04.1988", - "role": "Storage", - "admin": false, - "imageName": "UserProfile" - } -] diff --git a/stillasMobileApplication/stillasMobileApplication/Views/BlurView/BlurView.swift b/stillasMobileApplication/stillasMobileApplication/Views/BlurView/BlurView.swift index 5f5c1d116a7b1e71cd928bfceccef98035f335e9..2863563b9d9645339cdd7d7c3609ef21275edc3b 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/BlurView/BlurView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/BlurView/BlurView.swift @@ -7,7 +7,8 @@ import SwiftUI -// Blurred View +/// **BlurView** +/// A blur view struct BlurView: UIViewRepresentable { let style: UIBlurEffect.Style diff --git a/stillasMobileApplication/stillasMobileApplication/Views/FetchingApiData/ViewController.swift b/stillasMobileApplication/stillasMobileApplication/Views/FetchingApiData/ViewController.swift deleted file mode 100644 index f8df0e66d670c2b8557cc19935e428cbd0ce57d8..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/FetchingApiData/ViewController.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// ViewController.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 06/04/2022. -// -/* -import Foundation -import UIKit - -class ViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - } -} - -let url = "http://10.212.138.205:8080/stillastracking/v1/api/unit/" -var project: Project -*/ - - diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CheckBoxRow.swift b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CheckBoxRow.swift index e13ea2b834cfbbe29df9c8796a997b29957266a4..60ead4b2b96d2e5e882df7b768b1d583f4f81887 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CheckBoxRow.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CheckBoxRow.swift @@ -7,6 +7,8 @@ import SwiftUI +/// **CheckBoxRow** +/// Creates a checkbox row to be used in lists etc. struct CheckBoxRow: View { var title: String @Binding var selectedItems: Set<String> @@ -29,6 +31,8 @@ struct CheckBoxRow: View { } } +/// **CheckBoxView** +/// Creates the view of the checkboxes struct CheckBoxView: View { @Binding var checked: Bool @State var title: String diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CircleImage.swift b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CircleImage.swift index 788238b206ad32fe97159a1fae544bf5c617dd8c..0a65c93d29c7f2ce192cc0b61595556444f05738 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CircleImage.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CircleImage.swift @@ -7,6 +7,8 @@ import SwiftUI +/// **CircleImage** +/// Creates a circle frame with an image struct CircleImage: View { var image: Image diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CornerRadiusStyle.swift b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CornerRadiusStyle.swift new file mode 100644 index 0000000000000000000000000000000000000000..6d9d3d6408e34195fc4e81d4dbdde3f141d13a8f --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CornerRadiusStyle.swift @@ -0,0 +1,43 @@ +// +// CornerRadius.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 29/04/2022. +// + +import SwiftUI + +/// **CornedRadiusStyle** +/// Custom corner radius for corner +struct CornerRadiusStyle: ViewModifier { + var radius: CGFloat + var corners: UIRectCorner + + struct CornerRadiusShape: Shape { + + var radius = CGFloat.infinity + var corners = UIRectCorner.allCorners + + func path(in rect: CGRect) -> Path { + let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) + return Path(path.cgPath) + } + } + + func body(content: Content) -> some View { + content + .clipShape(CornerRadiusShape(radius: radius, corners: corners)) + } +} + +extension View { + func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { + ModifiedContent(content: self, modifier: CornerRadiusStyle(radius: radius, corners: corners)) + } +} +/* +struct CornerRadius_Previews: PreviewProvider { + static var previews: some View { + CornerRadius() + } +}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CustomCorners.swift b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CustomCorners.swift deleted file mode 100644 index ed7e837a6d1ecd7ff3333665c24235138a6b79bc..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/CustomCorners.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// CustomCorners.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 24/03/2022. -// - -import SwiftUI - -struct CustomCorners: Shape { - var corners: UIRectCorner - var radius: CGFloat - - func path(in rect: CGRect) -> Path { - let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) - - return Path(path.cgPath) - } -} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Helpers/TextFieldModifiers.swift b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/TextFieldModifiers.swift new file mode 100644 index 0000000000000000000000000000000000000000..6224cd3409b45c58e142605861ee89d4b81a891a --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/Helpers/TextFieldModifiers.swift @@ -0,0 +1,117 @@ +// +// TextFieldModifiers.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 09/05/2022. +// + +import SwiftUI + +/// **SearchBar** +/// UIViewRepresentable which adds a searchbar to a list in transfere scaffolding +/// This is taken from: +/// https://roddy.io/2020/09/07/add-search-bar-to-swiftui-picker/ +struct SearchBar: UIViewRepresentable { + + @Binding var text: String + var placeholder: String + + /// Makes a searchbar + func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar { + let searchBar = UISearchBar(frame: .zero) + searchBar.delegate = context.coordinator + + searchBar.placeholder = placeholder + searchBar.autocapitalizationType = .none + searchBar.searchBarStyle = .minimal + return searchBar + } + + /// Updates the searchbar + func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) { + uiView.text = text + } + + func makeCoordinator() -> SearchBar.Coordinator { + return Coordinator(text: $text) + } + + class Coordinator: NSObject, UISearchBarDelegate { + + @Binding var text: String + + init(text: Binding<String>) { + _text = text + } + + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + text = searchText + } + } +} + +// TODO: Make this happen/work - optionally remove +struct NoProjectSelected: TextFieldStyle { + @Binding var focused: Bool + func _body(configuration: TextField<Self._Label>) -> some View { + configuration + .padding(10) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .stroke(focused ? Color.red : Color.gray, lineWidth: 1) + ).padding() + } +} + +/// **TextFieldEmpty** +/// Checks if the textfield is empty, if so - make the border red +/// Code taken from and inspired from: +/// https://stackoverflow.com/questions/60379010/how-to-change-swiftui-textfield-style-after-tapping-on-it +struct TextFieldEmpty: TextFieldStyle { + @Binding var empty: Bool + func _body(configuration: TextField<Self._Label>) -> some View { + configuration + .padding(10) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .stroke(empty ? Color.red : Color.gray, lineWidth: 1) + ).padding() + } +} + +/// **ClearButton** +/// The clear button for the textfield +/// Code taken from and inspired from: https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwjzjJC2tMP3AhUnSfEDHSwjC-0QFnoECAUQAQ&url=https%3A%2F%2Fsanzaru84.medium.com%2Fswiftui-how-to-add-a-clear-button-to-a-textfield-9323c48ba61c&usg=AOvVaw1aPoAd3QYr5ByERti3mGWj +struct ClearButton: ViewModifier { + /// The searchfield text + @Binding var text: String + + public func body(content: Content) -> some View { + ZStack(alignment: .trailing) { + content + if !text.isEmpty { + Button(action: { + self.text = "" + }) { + Image(systemName: "delete.left") + .foregroundColor(Color(UIColor.opaqueSeparator)) + } + .padding(.trailing, 20) + } + } + } +} + +/// **NumbersOnly** +/// Validates textfield input to be Int +class NumbersOnly: ObservableObject { + @Published var value = "" { + didSet { + let filtered = value.filter { $0.isNumber } + + if value != filtered { + value = filtered + } + } + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapDisplay.swift b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapDisplay.swift index 363c72774c941255801b18584f01dc620c45fe37..ff938d185bc29e2049dae4396fcfb865147ebda2 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapDisplay.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapDisplay.swift @@ -10,20 +10,22 @@ import MapKit import CoreLocation -/** - MapDisplay - makes a MKMapView and defines its properties like userTrackingMode and setRegion etc. - - This way to define a map was inspired by this resource, as it creates a MKMapView in a neat way. It also makes the process of displaying CheckPoints and GeoFences easier alongside with other map functionality. - https://iosapptemplates.com/blog/swiftui/map-view-swiftui - */ +/// **MapDisplay** +/// Makes a MKMapView and defines its properties like userTrackingMode and setRegion etc. +/// This way to define a map was inspired by this resource, as it creates a MKMapView in a neat way. It also makes the process of displaying CheckPoints and GeoFences easier alongside with other map functionality. +/// https://iosapptemplates.com/blog/swiftui/map-view-swiftui struct MapDisplay: UIViewRepresentable { /// A property wrapper type that instantiates an observable object of type MapViewModel() @StateObject private var viewModel = MapViewModel() - - /** - makeUIView() - Makes the MKMapView - Allows to show user location, sets tracking mode and region of interest on "open" - */ + @State var projects: [Project] = [Project]() + + + + + /// Makes the MKMapView + /// Allows to show user location, sets tracking mode and region of interest on "open" + /// - Parameter context: A context structure containing information about the current state of the system + /// - Returns: A MKMapView func makeUIView(context: Context) -> MKMapView { let mapView = MKMapView(frame: UIScreen.main.bounds) mapView.showsUserLocation = true @@ -33,23 +35,24 @@ struct MapDisplay: UIViewRepresentable { return mapView } + /// Updates the MKMapView + /// - Parameter uiView: The MKMapView to be updated func updateUIView(_ uiView: MKMapView) { - let projVi = ProjectView() - - let annotations = projVi.projectsArr.map { project -> MKAnnotation in + //ProjectData().loadData(completion: @escaping projects) + let annotations = ProjectListView().projects.map { project -> MKAnnotation in let annotation = MKPointAnnotation() annotation.title = project.projectName annotation.subtitle = "\(project.projectID)" annotation.coordinate = CLLocationCoordinate2D(latitude: project.latitude, longitude: project.longitude) + print(annotation) return annotation } uiView.addAnnotations(annotations) } - /** - updateUIView() - Updates the state of the MKMapView with the changed information from SwiftUI - */ + /// **updateUIView** + /// Updates the state of the MKMapView with the changed information from SwiftUI func updateUIView(_ uiView: MKMapView, context: Context) { + } } - diff --git a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapView.swift b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapView.swift index 93c229be08433816a3790550ad8e8f8b5d306c92..c7f5c0f7f3877d5ce42e71bb7cc832fc7aac7e88 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapView.swift @@ -9,15 +9,15 @@ import SwiftUI import UIKit import MapKit -/** - A MapView responsible for displaying the Apple Maps in the application. - */ +/// **MapView** +/// Is responsible for displaying the Apple Maps in the application. struct MapView: View { /// A property wrapper type that instantiates an observable object of type MapViewModel() @StateObject private var viewModel = MapViewModel() @State private var searchText = "" @State private var dismissedAlready = false - + //@State var projects = [Project]() + var body: some View { VStack { GeometryReader { proxy in @@ -27,6 +27,7 @@ struct MapView: View { .onAppear { viewModel.checkIfLocationServicesIsEnabled() } + /// Displays an alert to the user if the location services are disabled, recommending the user to enable them and suggesting a redirect to the location service settings for the application. /// This alert is inspired from: https://www.hackingwithswift.com/forums/swiftui/getting-error-when-trying-to-change-location-authorisation/9216 .alert(isPresented: $viewModel.locationPermissionDenied, @@ -40,20 +41,25 @@ struct MapView: View { secondaryButton: .cancel(Text("Dismiss"), action: { setLocationPermissionFalse() })) }) + /*.task { + await ProjectData().loadData { (projects) in + self.projects = projects + } + }*/ } } + } - /** - Dismisses the alert - */ + + /// Dismisses the alert func setLocationPermissionFalse() { viewModel.locationPermissionDenied = false } +} - struct MapView_Previews: PreviewProvider { - static var previews: some View { - MapView() - } +struct MapView_Previews: PreviewProvider { + static var previews: some View { + MapView() } } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapViewModel.swift b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapViewModel.swift index 78e89915f3e1df90d13dad040e04980ef5947e8d..8fcbbc2b966f4343138777dac3236046aaa71911 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapViewModel.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/MapView/MapViewModel.swift @@ -9,10 +9,9 @@ import Foundation import MapKit import SwiftUI -/** - MapDetails - enum values that are used multiple places. - Used to abstract reused code - */ +/// **MapDetails** +/// Enum values that are used multiple places. +/// Used to abstract reused code. enum MapDetails { /// Sets the starting location of the map to be Gjøvik (latitude: 60.79574, longitude: 10.69155) static let startingLocation = CLLocationCoordinate2D( @@ -20,18 +19,17 @@ enum MapDetails { longitude: 10.69155 ) /// Sets zoom level of map on initialization - /// Closer to zero is more zoomed in + /// Closer to zero is more zoomed in static let defaultSpan = MKCoordinateSpan( latitudeDelta: 0.03, longitudeDelta: 0.03 ) } -/** - Class responsible for checking if the user has enabled location services. - This class is inspired a lot by the Apple Development documentation as well as this youtube video: - https://www.youtube.com/watch?v=hWMkimzIQoU - */ +/// **MapViewModel** +/// Class responsible for checking if the user has enabled location services. +/// This class is inspired a lot by the Apple Development documentation as well as this youtube video: +/// https://www.youtube.com/watch?v=hWMkimzIQoU final class MapViewModel: NSObject, ObservableObject, CLLocationManagerDelegate { @Published var locationPermissionDenied = false @Published var dismissCount = 0 @@ -45,9 +43,7 @@ final class MapViewModel: NSObject, ObservableObject, CLLocationManagerDelegate var locationManager: CLLocationManager? - /** - checkIfLocationServicesIsEnabled() - Checks if the user has enabled location services. - */ + /// checkIfLocationServicesIsEnabled() - Checks if the user has enabled location services. func checkIfLocationServicesIsEnabled() { if CLLocationManager.locationServicesEnabled() { locationManager = CLLocationManager() @@ -61,9 +57,8 @@ final class MapViewModel: NSObject, ObservableObject, CLLocationManagerDelegate } // TODO: Check -> fatal error when location services are off? - /** - checkLocationAuthorization() - Checks which authorization the application is assigned to. - */ + /// checkLocationAuthorization + /// Checks which authorization the application is assigned to. private func checkLocationAutorization() { guard let locationManager = locationManager else { return } @@ -90,9 +85,7 @@ final class MapViewModel: NSObject, ObservableObject, CLLocationManagerDelegate } } - /** - locationManagerDidChangeAuthorization() - Checks the autorization on locationManager creation as well as if the apps authorization changes - */ + /// locationManagerDidChangeAuthorization() - Checks the autorization on locationManager creation as well as if the apps authorization changes func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { checkLocationAutorization() } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/MapView_Old/MapView_Old.swift b/stillasMobileApplication/stillasMobileApplication/Views/MapView_Old/MapView_Old.swift deleted file mode 100644 index db8e1365409ebf18ce061b895da2def2a7de33b5..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/MapView_Old/MapView_Old.swift +++ /dev/null @@ -1,246 +0,0 @@ -// -// MapView.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 24/03/2022. -// -/* -import SwiftUI -import UIKit -import MapKit - - -struct ScaffoldingUnit: Identifiable { - let id = UUID() - let name: String - let size: String - let amount: Int -} - -struct ScaffoldingUnitRow: View { - var scaffolding: ScaffoldingUnit - - var body: some View { - HStack { - VStack(alignment: .leading) { - Text(scaffolding.name) - Text(scaffolding.size).font(.subheadline).foregroundColor(.gray) - } - Spacer() - Text(String(format: "%d", scaffolding.amount)) - .foregroundColor(.gray) - } - } -} - -/** - A MapView responsible for displaying the Apple Maps in the application. - - Inspiration taken from this youtube video: - https://www.youtube.com/watch?v=CyMtjSspJZA - */ -struct MapView_Old: View { - @State private var isInitialOffsetSet = false - @State private var searchText = "" - - var body: some View { - ZStack { - GeometryReader { proxy in - /// MapViewMap responsible for the map view - MapViewMap() - - /// DrawerView responsible for the drawer slide - DrawerView() - } - } -} - - struct DrawerView: View { - //@State var searchQuery = "" - @State var offset: CGFloat = 0 - @State var lastOffset: CGFloat = 0 - @GestureState var gestureOffset: CGFloat = 0 - @State private var isInitialOffsetSet = false - let height = 0 - - - /*let scaffoldingUnits = [ - ScaffoldingUnit(name: "Spir", size: "2m", amount: 1400), - ScaffoldingUnit(name: "Spir", size: "3m", amount: 1500), - ScaffoldingUnit(name: "Lengdebjelke", size: "3m", amount: 480) - /*"Spir 3m", "Spir 2m", "Bærebjelke", "Trapp", "UTV Trapp", "Bunnskrue", "Diagonalstag DS", "Stillaslem Alu", "AL plank B-230 mm", "Rekkverk", "Enrørsbjelke", "Horisontaler"*/ - ]*/ - - - var body: some View { - GeometryReader { proxy in - let height = proxy.frame(in: .global).height - ZStack { - BlurView(style: .systemMaterial) - .clipShape(CustomCorners(corners: [.topLeft, .topRight], radius: 15)) - - VStack { - Capsule() - .fill(Color.gray) - .frame(width: 40, height: 5) - .padding(.top, 7) - - NavView() - /*ZStack { - NavigationView { - List (searchResults) { scaffolding in - NavigationLink(destination: DetailView(scaffolding: scaffolding)) { ScaffoldingUnitRow (scaffolding: scaffolding) } - } - //.listRowBackground(Color.red) - .listStyle(PlainListStyle()) - .searchable(text: $searchQuery) - .navigationTitle("Scaffolding units") - } - .navigationViewStyle(StackNavigationViewStyle()) - //.ignoresSafeArea(.all, edges: .horizontal) - //.frame(minWidth: 0, idealWidth: proxy.frame(in: .global).width, maxWidth: .infinity, minHeight: 0, idealHeight: proxy.frame(in: .global).height, maxHeight: .infinity, alignment: .topLeading) - - }*/ - } - .padding(.horizontal) - .frame(maxHeight: .infinity, alignment: .top) - } - .offset(y: height - 100) - .offset(y: -offset > 0 ? -offset <= (height - 100) ? offset : -(height - 100) : 0) - .gesture(DragGesture().updating($gestureOffset, body: {value, out, _ in - out = value.translation.height - /// onChangeDrawer() updates the offset when a gesture was performed - onChangeDrawer() - }).onEnded({ value in - let maxHeight = height - 100 - /// When the gesture ends, update the placement of the drawer view to fixed position - withAnimation { - if -offset > 100 && -offset < maxHeight / 2 { - offset = -(maxHeight / 3) - } - else if (-offset > maxHeight / 2) { - offset = -maxHeight - } - else { - offset = 0 - } - } - lastOffset = offset - })) - } - .ignoresSafeArea(.all, edges: .bottom) - } - - - /** - onChangeDrawer() resposible for updating the offset when a gesture is performed - */ - func onChangeDrawer (){ - DispatchQueue.main.async { - self.offset = gestureOffset + lastOffset - } - } - /*var searchResults: [ScaffoldingUnit] { - if searchQuery.isEmpty { - return scaffoldingUnits.sorted { $0.name < $1.name } - } else { - return scaffoldingUnits.filter { $0.name.contains(searchQuery) }.sorted { $0.name < $1.name } - } - }*/ - } - - /** - MapViewMap creates a view containing the map - */ - struct MapViewMap: View { - /// Sets the starting location to be Gjøvik (latitude: 60.79574, longitude: 10.69155) - @State var region = MKCoordinateRegion ( - center: CLLocationCoordinate2D( - latitude: 60.79574, - longitude: 10.69155 - ), - /// The zoom level of the application when opened - /// Closer to 0 means greater zoom level - span: MKCoordinateSpan( - latitudeDelta: 0.03, - longitudeDelta: 0.03 - ) - ) - var body: some View { - Map(coordinateRegion: $region) - .ignoresSafeArea() - } - } - - struct MapView_Previews: PreviewProvider { - static var previews: some View { - MapView() - } - } -} - - struct DetailView: View { - var scaffolding: ScaffoldingUnit - - var body: some View { - VStack { - Text(scaffolding.name).font(.title) - - HStack { - Text("\(scaffolding.size) - \(String(format: "%d", scaffolding.amount))") - } - - Spacer() - } - } - } - -struct NavView: View { - @State var searchQuery = "" - - let scaffoldingUnits = [ - ScaffoldingUnit(name: "Spir", size: "2m", amount: 1400), - ScaffoldingUnit(name: "Spir", size: "3m", amount: 1500), - ScaffoldingUnit(name: "Lengdebjelke", size: "3m", amount: 480) - /*"Spir 3m", "Spir 2m", "Bærebjelke", "Trapp", "UTV Trapp", "Bunnskrue", "Diagonalstag DS", "Stillaslem Alu", "AL plank B-230 mm", "Rekkverk", "Enrørsbjelke", "Horisontaler"*/ - ] - - var body: some View { - ZStack { - NavigationView { - List (searchResults) { scaffolding in - NavigationLink(destination: DetailView(scaffolding: scaffolding)) { ScaffoldingUnitRow (scaffolding: scaffolding) } - } - .listStyle(PlainListStyle()) - .searchable(text: $searchQuery) - .navigationTitle("Scaffolding units") - } - .navigationViewStyle(.stack) - } - } - - var searchResults: [ScaffoldingUnit] { - if searchQuery.isEmpty { - return scaffoldingUnits.sorted { $0.name < $1.name } - } else { - return scaffoldingUnits.filter { $0.name.contains(searchQuery) }.sorted { $0.name < $1.name } - } - } - -} - -extension UINavigationController { - override open func viewDidLoad() { - super.viewDidLoad() - - overrideUserInterfaceStyle = .unspecified - - let appearance = UINavigationBarAppearance() - appearance.backgroundColor = UIColor.clear - - navigationBar.standardAppearance = appearance - navigationBar.compactAppearance = appearance - navigationBar.scrollEdgeAppearance = appearance - } -} -*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProfileData.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProfileData.swift new file mode 100644 index 0000000000000000000000000000000000000000..618f05fe0355c1bdf3b23e41dc584eb087c35807 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProfileData.swift @@ -0,0 +1,44 @@ +// +// ProfileData.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 10/05/2022. +// + +import SwiftUI +import Foundation + +/// **ProfileData** +/// Gets the data about the logged in user from the API +class ProfileData: ObservableObject { + /// Is data loading? + @Published private var _isLoadingProfile: Bool = false + + /// Getter for the _isLoadingProfile + var isLoadingProfile: Bool { + get { return _isLoadingProfile} + } + + /// Responsible for getting the data about the logged in user from the API + /// - Parameters: + /// - userID: the userID of the logged in user from Firebase Authentication + /// - completion: completion handler + func loadData(userID: String, completion:@escaping (Profile) -> ()) async { + _isLoadingProfile = true + print("One = \(_isLoadingProfile)") + + guard let url = URL(string: "http://10.212.138.205:8080/stillastracking/v1/api/user?id=\(userID)") else { + print("Invalid url...") + return + } + /// Sends the request and gets the data + URLSession.shared.dataTask(with: url) { [self] data, response, error in + let profile = try! JSONDecoder().decode(Profile.self, from: data!) + DispatchQueue.main.async { + completion(profile) + self._isLoadingProfile = false + print("Two = \(self._isLoadingProfile)") + } + }.resume() + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProjectData.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProjectData.swift new file mode 100644 index 0000000000000000000000000000000000000000..4fc8cd32fa84f409afbfa881043ebe4b969b6e50 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/FetchingApiData/ProjectData.swift @@ -0,0 +1,44 @@ +// +// ProjectData.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 07/04/2022. +// + +import SwiftUI +import Foundation + +/// **ProjectData** +/// Retrieves all the projects and its data from the API +class ProjectData: ObservableObject { + //@Published var projects = [Project]() // TODO: REMOVE IF NOT USED IN THE END + /// Is data loading? + @Published private var _isLoading: Bool = false + + /// Getter for the _isLoading + var isLoading: Bool { + get { return _isLoading} + } + + + /// Responsible for getting the data about projects from the API + /// - Parameter completion: completion handler + func loadData(completion:@escaping ([Project]) -> ()) async { + _isLoading = true + print("One = \(_isLoading)") + + guard let url = URL(string: "http://10.212.138.205:8080/stillastracking/v1/api/project?scaffolding=true") else { + print("Invalid url...") + return + } + /// Sends the request and gets the data + URLSession.shared.dataTask(with: url) { [self] data, response, error in + let projects = try! JSONDecoder().decode([Project].self, from: data!) + DispatchQueue.main.async { + completion(projects) + self._isLoading = false + print("Two = \(self._isLoading)") + } + }.resume() + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/ModelData.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/ModelData.swift deleted file mode 100644 index f88d42b51518fef626ea99ec99adead88f0afed7..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/ModelData.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// ModelData.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 28/03/2022. -// - -import Foundation -import Combine - -/** - ModelData responsible for loading/decoding the user information from the data/json object - To update views when data changes, you make your data model classes observable objects. - */ -final class ModelData: ObservableObject { - @Published var users: [User] = load("userData.json") -} - -/// load<T: Decodable>() - function for decoding the data into json object -/// Uses guard and do/catch methods to make sure to catch potential errors while retreiving or decoding the data -func load<T: Decodable>(_ filename: String) -> T { - let data: Data - - guard let file = Bundle.main.url(forResource: filename, withExtension: nil) - else { - fatalError("Couldn't find \(filename) in main bundle.") - } - - do { - data = try Data(contentsOf: file) - } catch { - fatalError("Couldn't load \(filename) from main bundle:\n\(error)") - } - - do { - let decoder = JSONDecoder() - return try decoder.decode(T.self, from: data) - } catch { - fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") - } -} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/Profile.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/Profile.swift new file mode 100644 index 0000000000000000000000000000000000000000..c8c45a412044c311bf7e17e74a4bec3bf642cb39 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/Profile.swift @@ -0,0 +1,21 @@ +// +// Profile.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 10/05/2022. +// + +import Foundation + +struct Profile: Codable { + let employeeID: String + var name: Name + let dateOfBirth, role: String + let phone: Int + let email: String + let admin: Bool +} + +struct Name: Codable { + let firstName, lastName: String +} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/Project.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/Project.swift index b466a92b304949f37447567d367c1302cd9e42da..0d5a17cb6a487a2af73d1c8033f3339bfe140ae0 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/Project.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/Project.swift @@ -8,13 +8,6 @@ import Foundation import SwiftUI -/* -struct Response: Codable{ - var results: [Project] -} - */ - -// MARK: - Project struct Project: Codable { let projectID: Int let projectName: String @@ -28,19 +21,16 @@ struct Project: Codable { let scaffolding: [Scaffolding]? } -// MARK: - Address struct Address: Codable { let street, zipcode, municipality, county: String } -// MARK: - Customer struct Customer: Codable { let name: String let number: Int let email: String } -// MARK: - Geofence struct Geofence: Codable { let wPosition, xPosition, yPosition, zPosition: Position @@ -52,17 +42,14 @@ struct Geofence: Codable { } } -// MARK: - Position struct Position: Codable { let latitude, longitude: Double } -// MARK: - Period struct Period: Codable { let startDate, endDate: String } -// MARK: - Scaffolding struct Scaffolding: Codable { let type: String let quantity: Quantity @@ -73,7 +60,6 @@ struct Scaffolding: Codable { } } -// MARK: - Quantity struct Quantity: Codable { let expected, registered: Int } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/ProjectData.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/ProjectData.swift deleted file mode 100644 index 7a531c183e886fb6aee873967ae075053f468f13..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/ProjectData.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// ProjectData.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 07/04/2022. -// - -import SwiftUI -import Foundation - -/** - ProjectData responsible for loading/decoding the project information from the API - To update views when data changes, you make your data model classes observable objects. - */ -class ProjectData: ObservableObject { - @Published var projects = [Project]() - - func loadData(completion:@escaping ([Project]) -> ()) async { - guard let url = URL(string: "http://10.212.138.205:8080/stillastracking/v1/api/project") else { - print("Invalid url...") - return - } - URLSession.shared.dataTask(with: url) { data, response, error in - let projects = try! JSONDecoder().decode([Project].self, from: data!) - DispatchQueue.main.async { - completion(projects) - } - }.resume() - } -} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/Scaffolding.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/Scaffolding.swift deleted file mode 100644 index 270485fb021c9968dc516a339f562c2d9a2f19f5..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/Scaffolding.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Scaffolding.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 28/03/2022. -// - -import SwiftUI -import Foundation -import CoreLocation -/* -struct Scaffolding: Hashable, Codable, Identifiable { - var id: Int - var type: String - var isFeatured: Bool - - var category: Category - enum Category: String, CaseIterable, Codable { - case flooring = "Flooring" - case spire = "Spire" - case railing = "Railing" - } - - private var imageName: String - var image: Image { - Image(imageName) - } - - var featureImage: Image? { - isFeatured ? Image(imageName + "_feature") : nil - } - - private var coordinates: Coordinates - - var locationCoordinate: CLLocationCoordinate2D { - CLLocationCoordinate2D( - latitude: coordinates.latitude, - longitude: coordinates.longitude - ) - } - - struct Coordinates: Hashable, Codable { - var latitude: Double - var longitude: Double - } - - var batteryLevel: Int -}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/ScaffoldingTransfere.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/ScaffoldingTransfere.swift new file mode 100644 index 0000000000000000000000000000000000000000..fced020bdbab9158a61a32f22f5ceea4789712ba --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/ScaffoldingTransfere.swift @@ -0,0 +1,18 @@ +// +// Scaffolding.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 28/03/2022. +// + +import Combine + +struct Scaff: Codable { + let scaffold: [Move] + let toProjectID, fromProjectID: Int +} + +struct Move: Codable { + let type: String + let quantity: Int +} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Model/User.swift b/stillasMobileApplication/stillasMobileApplication/Views/Model/User.swift index 717f0ecb01331fef9df16eb57c17b42647fac486..ad6c33d1604fde689e12390a38c6d011c79515bd 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/Model/User.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/Model/User.swift @@ -8,9 +8,6 @@ import SwiftUI import Foundation -/** - User - decode Data into struct - */ struct User: Codable, Hashable, Identifiable { var id: Int var name: String diff --git a/stillasMobileApplication/stillasMobileApplication/Views/NavigationBarBottom.swift b/stillasMobileApplication/stillasMobileApplication/Views/NavigationBarBottom.swift index 1d78a1479a8aa435f5ae84da92a0e24c806dc0c1..c4e048d871c94e05465428417d72ba60f312f5b9 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/NavigationBarBottom.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/NavigationBarBottom.swift @@ -7,40 +7,44 @@ import SwiftUI +/// **NavigationBarBottom** +/// The navigationbar View responsible for the navigation between the three main views Project, Map and Profile struct NavigationBarBottom: View { - @State private var selection: Tab = .map + /// Sets selection to project + @State private var selection: Tab = .project + + /// All projects @State var projects = [Project]() + + /// Enum for the different pages enum Tab { case project case map case profile - case projectViewAPI } - var body: some View { - TabView(selection: $selection) { - ProjectView() + ProjectListView() .tabItem { - Label("Project", systemImage: "square.grid.2x2") + Label("Prosjekt", systemImage: "square.grid.2x2") } .tag(Tab.project) MapView() .tabItem { - Label("Map", systemImage: "map") + Label("Kart", systemImage: "map") } .tag(Tab.map) ProfileView() .tabItem { - Label("Profile", systemImage: "person.crop.circle") + Label("Profil", systemImage: "person.crop.circle") } .tag(Tab.profile) } .onAppear() { - /// https://www.bigmountainstudio.com/community/public/posts/86559-how-to-customize-the-background-of-the-tabview-in-swiftui + /// The transparrent effect is taken from: https://www.bigmountainstudio.com/community/public/posts/86559-how-to-customize-the-background-of-the-tabview-in-swiftui let appearance = UITabBarAppearance() appearance.backgroundEffect = UIBlurEffect(style: .systemThinMaterial) // Use this appearance when scrolling behind the TabView: @@ -54,6 +58,6 @@ struct NavigationBarBottom: View { struct NavigationBarBottom_Previews: PreviewProvider { static var previews: some View { NavigationBarBottom() - .environmentObject(ModelData()) + //.environmentObject(ModelData()) } } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProfileView/ProfileView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProfileView/ProfileView.swift index 8e21ce68121b45814dafab63c4ac6b04903aac05..ab195642e6b3d88083b5198381ce080f8c9c7f63 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProfileView/ProfileView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProfileView/ProfileView.swift @@ -7,30 +7,28 @@ import SwiftUI -/** - ProfileView - Calls the ProfileDetails view containing information about a user - */ +/// **ProfileView** +/// Calls the ProfileDetails View containing information about a user struct ProfileView: View { var body: some View { VStack { - // TODO: Change input to not take the ModelData's first element only, but get info from API - ProfileDetails(user: ModelData().users[0]) + ProfileDetails() } } } -/** - ProfileDetails - A view responsible for the layout of the user information and showing the details about the user - */ +/// **ProfileDetails** +/// A View responsible for the layout of the user information and showing the details about the user struct ProfileDetails: View { - @EnvironmentObject var modelData: ModelData - - var user: User - - /// Retrieves the user from the json object with ID equal to the object passed into the voew - var userIndex: Int { - modelData.users.firstIndex(where: { $0.id == user.id })! - } + /// Darkmode or lightmode? + @Environment(\.colorScheme) var colorScheme + + /// Models + @EnvironmentObject var viewModel: AppViewModel + @ObservedObject var profileModel: ProfileData = ProfileData() + + /// Initializes a user object + @State var user: [Profile] = [Profile]() var body: some View { ScrollView { @@ -38,47 +36,137 @@ struct ProfileDetails: View { MapView() .ignoresSafeArea(edges: .top) .frame(height: 300) - /// CircleImage responsible for displaying the user profile image - CircleImage(image: user.image) + CircleImage(image: Image("UserProfile")) .offset(y: -130) .padding(.bottom, -130) - /// A VStack used to display all the user profile data - VStack(alignment: .leading) { - HStack { - Text(user.name) - .font(.largeTitle) - } - - HStack { - // TODO: Change to not hard coded values when API is updated - Text("MBStillas") - //.font(.subheadline) - Spacer() - Text("Role: \(user.role)") - //.font(.subheadline) - } - //.font(.subheadline) - .foregroundColor(.secondary) + /// If there are user data + if (!user.isEmpty) { - Divider() + VStack { + VStack { + Image(systemName: "person.crop.circle.badge.checkmark") + .resizable() + .frame(width: 35, height: 30) + .foregroundColor(.blue) + + Text("Bruker info") + .font(Font.system(size: 20).bold()) + .padding(.bottom, 2) + + Text("Nedenfor finner du brukerinformasjonen din.") + .font(.caption) + .foregroundColor(Color.gray) + .padding(.bottom, 5) + } + + VStack { + HStack { + Text("\(user[0].name.firstName) \(user[0].name.lastName)") + } + .font(.title3.bold()) + } + .padding(.bottom, 5) + + VStack { + Text("\(user[0].employeeID)") + .font(.body) + + Text("ANSATT NUMMER") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(user[0].dateOfBirth)") + .font(.body) + + Text("FØDSELSNUMMER") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + VStack { + Text("\(user[0].role)") + .font(.body) + + Text("ROLLE") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(user[0].admin.description)") + .font(.body) + + Text("ADMIN") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + } + .padding() + .frame(width: (UIScreen.screenWidth / 1.2), alignment: .center) + .contentShape(RoundedRectangle(cornerRadius: 5)) + .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) + VStack { - Text("Date of birth") - .font(.title2) - Text("\(user.dateOfBirth)") - .foregroundColor(.secondary) + VStack { + Image(systemName: "person.circle") + .resizable() + .frame(width: 30, height: 30) + .foregroundColor(.blue) + + Text("Kontakt info") + .font(Font.system(size: 20).bold()) + .padding(.bottom, 2) + + Text("Nedenfor finner du kontaktinformasjonen din.") + .font(.caption) + .foregroundColor(Color.gray) + .padding(.bottom, 5) + } + + VStack { + Text("\(user[0].phone)") + .font(.body) + + Text("TELEFONNUMMER") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(user[0].email)") + .font(.body) + + Text("EMAIL") + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) } - - Spacer() + .padding() + .frame(width: (UIScreen.screenWidth / 1.2), alignment: .center) + .contentShape(RoundedRectangle(cornerRadius: 5)) + .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) + } + } + .task { + /// Get user data from the API with the logged in users ID + await profileModel.loadData(userID: viewModel.userID) { (user) in + self.user.append(user) } - .padding() - - Spacer() } - .navigationTitle(user.name) - .navigationBarTitleDisplayMode(.inline) + .ignoresSafeArea(edges: .top) } } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/AreaFilter/FilterProjectArea.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/AreaFilter/FilterProjectArea.swift index 73444efb81381bdd24e30ab85582f7c9789f9277..98e3bb5ec1136b2aef72a14cd1472cf8403a6007 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/AreaFilter/FilterProjectArea.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/AreaFilter/FilterProjectArea.swift @@ -7,16 +7,26 @@ import SwiftUI +/// **FilterProjectArea** +/// The View for selecting a project with area set to a value struct FilterProjectArea: View { + + /// Is the box checked? @State private var checked: [Bool] + + /// All the selected counties @Binding var selArr: [String] + + /// Area filter active @Binding var areaFilterActive: Bool + /// All counties to filter based of let counties = ["Agder", "Innlandet", "Møre og Romsdal", "Nordland", "Oslo", "Rogaland", "Vestfold og Telemark", "Troms og Finnmark", "Trøndelag", "Vestlandet", "Viken"] - // selectedItems gets updated by the CheckBoxRow as it changes - @State var selectedItems: Set<String> = [] // Use a Set to keep track of multiple check boxes + /// selectedItems gets updated by the CheckBoxRow as it changes + @State var selectedItems: Set<String> = [] /// Use a Set to keep track of multiple check boxes + /// Initializes the selections to false so the boxes are unchecked as the user accesses the filter init(selArr: Binding<[String]>, areaFilterActive: Binding<Bool>) { self._selArr = selArr _checked = State(initialValue: [Bool](repeating: false, count: counties.count)) @@ -27,6 +37,7 @@ struct FilterProjectArea: View { VStack { VStack { List { + /// For each county, add it to the list with a checkbox and description ForEach(counties, id: \.self) { county in HStack { CheckBoxRow(title: county, selectedItems: $selectedItems, isSelected: selectedItems.contains(county)) @@ -40,6 +51,7 @@ struct FilterProjectArea: View { .padding(.bottom, 110) } .overlay(alignment: .bottom) { + /// Updates the parent View with the selected counties Button(action: { print(self.selectedItems) for selectedItem in selectedItems { diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterProjectData.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterProjectData.swift deleted file mode 100644 index 1e62eea9aaf6c92c72b0fc0ce982a55fbd1fde07..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterProjectData.swift +++ /dev/null @@ -1,146 +0,0 @@ -// -// FilterProjectData.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 14/04/2022. -// - -import UIKit -import SwiftUI -/* -enum FilterType { - case none, - period, - startBeforePeriod, - startAfterPeriod, - endBeforePeriod, - endAfterPeriod, - sizeEqualTo, - sizeLessThan, - sizeGreaterThan, - state, - county -}*/ - -struct FilterProjectData: View { - @State var projects = [Project]() - @State private var showFilterModalView: Bool = false - @State private var showAddProjectModalView: Bool = false - @State var filterArrArea: [String] = [] - - @State var filter: FilterType = .none - @State var filterArr: [String] = [] - - // TODO: Make these values operable - @State var projectStartDate = Date.distantPast - @State var projectEndDate = Date.distantFuture - @State var projectSize = 99999 - @State var projectState = "Active" - @State var projectCounty = "Innlandet" - - var body: some View { - VStack { - NavigationView { - Form { - Section(header: Text("All Projects")) { - List(filteredProjects, id: \.projectID) { project in - Text(project.projectName) - } - .navigationTitle("Projects") - //.listStyle(.grouped) - } - } - .listStyle(.grouped) - .toolbar { - ToolbarItemGroup(placement: .navigationBarLeading) { - Button(action: { - print("Filter tapped!") - self.showFilterModalView.toggle() - - }) { - Label("Filter", systemImage: "line.3.horizontal.decrease.circle") - } - } - - ToolbarItemGroup(placement: .navigationBarTrailing) { - Button(action: { - print("Add project tapped!") - self.showAddProjectModalView.toggle() - }) { - Label("Add", systemImage: "plus.circle") - } - } - } - .sheet(isPresented: $showFilterModalView, - onDismiss: didDismiss) { - FilterView(selStartDateBind: $projectStartDate, selEndDateBind: $projectEndDate, projectArea: $projectCounty, projectSize: $projectSize, projectStatus: $projectState, filterArr: $filterArr, filterArrArea: $filterArrArea) - .onChange(of: projectStartDate) { value in - filter = .period - } - } - .sheet(isPresented: $showAddProjectModalView, onDismiss: didDismiss) { - AddProjectView() - } - } - } - .task { - await ProjectData().loadData { (projects) in - self.projects = projects - } - } - } - - func didDismiss() { - - // Handle the dismissing action. - } - - var filteredProjects: [Project] { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "dd/MM/yy" - - switch filter { - case .none: - return projects - case .period: - //return projects.filter { $0.period.startDate > projectStartDate && $0.period.endDate < projectEndDate } - return projects.filter { dateFormatter.date(from: $0.period.startDate)! > projectStartDate && dateFormatter.date(from: $0.period.endDate)! < projectEndDate } - case .startBeforePeriod: - //return projects.filter { $0.period.startDate < projectStartDate } - return projects.filter { dateFormatter.date(from: $0.period.startDate)! < projectStartDate } - case .startAfterPeriod: - //return projects.filter { $0.period.startDate > projectStartDate } - return projects.filter { dateFormatter.date(from: $0.period.startDate)! > projectStartDate } - case .endBeforePeriod: - //return projects.filter { $0.period.endDate < projectEndDate } - return projects.filter { dateFormatter.date(from: $0.period.endDate)! < projectEndDate } - case .endAfterPeriod: - //return projects.filter { $0.period.endDate > projectEndDate } - return projects.filter { dateFormatter.date(from: $0.period.endDate)! > projectEndDate } - case .sizeEqualTo: - return projects.filter { $0.size == Int(projectSize) } - case .sizeLessThan: - return projects.filter { $0.size < Int(projectSize) } - case .sizeGreaterThan: - return projects.filter { $0.size > Int(projectSize) } - case .state: - return projects.filter { $0.state == projectState } - case .county: - return projects.filter { $0.address.county == projectCounty } - } - } -} - -struct AddProjectView: View { - var body: some View { - VStack { - Text("Add Project SheetView") - } - } -} -/* -struct FilterProjectData_Previews: PreviewProvider { - static var previews: some View { - FilterProjectData() - } -}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveAreaFilterView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveAreaFilterView.swift index 262b8c9aa5d19796af766ec3a5de3e986a23251c..3f98a295f5187151c11b25ae3b68844de6d0fd70 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveAreaFilterView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveAreaFilterView.swift @@ -7,6 +7,9 @@ import SwiftUI + +/// **ActiveAreaFilterView** +/// The view presented on top of the area-navigation row to display a preview of the selected areafilter. struct ActiveAreaFilterView: View { @Binding var filterArr: [String] @@ -14,36 +17,32 @@ struct ActiveAreaFilterView: View { var body: some View { + /// if there is a filter applied, display a preview of the selected filter if areaFilterActive { HStack { HStack { - //ScrollViewReader { scrollView in - ScrollView (.horizontal, showsIndicators: false) { - HStack { - Text("(\(filterArr.count))") - .padding(.leading, 4) - ForEach(filterArr.indices, id: \.self) { index in - HStack { - Text("\(filterArr[index])") - .lineLimit(1) - .padding(-3) - if index != filterArr.count-1 { - Text(",") - } + ScrollView (.horizontal, showsIndicators: false) { + HStack { + Text("(\(filterArr.count))") + .padding(.leading, 4) + ForEach(filterArr.indices, id: \.self) { index in + HStack { + Text("\(filterArr[index])") + .lineLimit(1) + .padding(-3) + if index != filterArr.count-1 { + Text(",") } } } } - /* - .onAppear { - scrollView.scrollTo(filterArr[filterArr.endIndex]) - } - }*/ + } } .font(.system(size: 11).bold()) .padding(.vertical, 5) .lineLimit(1) + /// Deletes the selected filter and removes it from the preview Button(action: { deleteFilterItem(filterItem: "area") self.areaFilterActive = false @@ -62,6 +61,8 @@ struct ActiveAreaFilterView: View { } } + /// Removes the filter from the array with filters + /// - Parameter filterItem: the selected filter you want to remove func deleteFilterItem(filterItem: String) { if let i = filterArr.firstIndex(of: filterItem) { filterArr.remove(at: i) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActivePeriodFilterView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActivePeriodFilterView.swift index 4f94152c3082cf8face639731661163ce7ee8f0f..c0411c2bec6eddc18136d0efd3ca3bf0a551bd86 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActivePeriodFilterView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActivePeriodFilterView.swift @@ -7,6 +7,8 @@ import SwiftUI +/// **ActivePeriodFilterView** +/// The view presented on top of the size-navigation row to display a preview of the selected periodfilter. struct ActivePeriodFilterView: View { @Binding var startDate: Date @Binding var endDate: Date @@ -17,6 +19,7 @@ struct ActivePeriodFilterView: View { var body: some View { + /// if there is a filter applied, display a preview of the selected filter if periodFilterActive { HStack { HStack { @@ -33,6 +36,7 @@ struct ActivePeriodFilterView: View { .font(.system(size: 11).bold()) .padding(.vertical, 5) + /// Deletes the selected filter and removes it from the preview Button(action: { deleteFilterItem(filterItem: "period") self.periodFilterActive.toggle() @@ -50,6 +54,8 @@ struct ActivePeriodFilterView: View { } } + /// Removes the filter from the array with filters + /// - Parameter filterItem: the selected filter you want to remove func deleteFilterItem(filterItem: String) { if let i = filterArr.firstIndex(of: filterItem) { filterArr.remove(at: i) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveSizeFilterView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveSizeFilterView.swift index e077658847652b7b8d84e58d82f4709714a5b787..a8477ed2756ad105365c980a7fc15bf1f7f50138 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveSizeFilterView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveSizeFilterView.swift @@ -7,6 +7,8 @@ import SwiftUI +/// **ActiveSizeFilterView** +/// The view presented on top of the size-navigation row to display a preview of the selected sizefilter. struct ActiveSizeFilterView: View { @Binding var filterArr: [String] @Binding var projectMinSize: Int @@ -15,12 +17,12 @@ struct ActiveSizeFilterView: View { @Binding var selection: String var body: some View { - + /// if there is a filter applied, display a preview of the selected filter if sizeFilterActive { HStack { HStack { ScrollView (.horizontal, showsIndicators: false) { - if selection == "Between" { + if selection == "Between" { /// display both minimum size and max size HStack { Text("\(projectMinSize) m") + Text("2") @@ -34,7 +36,7 @@ struct ActiveSizeFilterView: View { .font(Font.system(size: 10)) } .padding(.leading, 5) - } else if selection == "Less Than" { + } else if selection == "Less Than" { /// display only minimum size HStack { Text("Under ") + Text("\(projectMinSize) m") @@ -43,7 +45,7 @@ struct ActiveSizeFilterView: View { .font(Font.system(size: 10)) } .padding(.leading, 5) - } else if selection == "Greater Than" { + } else if selection == "Greater Than" { /// display only maximum size HStack { Text("Over ") + Text("\(projectMaxSize) m") @@ -59,6 +61,7 @@ struct ActiveSizeFilterView: View { .padding(.vertical, 5) .lineLimit(1) + /// Deletes the selected filter and removes it from the preview Button(action: { deleteFilterItem(filterItem: "size") self.sizeFilterActive = false @@ -78,6 +81,9 @@ struct ActiveSizeFilterView: View { } } + + /// Removes the filter from the array with filters + /// - Parameter filterItem: the selected filter you want to remove func deleteFilterItem(filterItem: String) { if let i = filterArr.firstIndex(of: filterItem) { filterArr.remove(at: i) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveStatusFilterView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveStatusFilterView.swift index b7a5e9e4e7c3f1df9823363905e992d47ae4417d..deac93f86ff5057782446d10f365fd4b65f56f19 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveStatusFilterView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/ActiveFilterViews/ActiveStatusFilterView.swift @@ -7,13 +7,15 @@ import SwiftUI +/// **ActiveStatusFilterView** +/// The view presented on top of the size-navigation row to display a preview of the selected statusfilter. struct ActiveStatusFilterView: View { @Binding var filterArr: [String] @Binding var projectStatus: String @Binding var statusFilterActive: Bool var body: some View { - + /// if there is a filter applied, display a preview of the selected filter if statusFilterActive { HStack { HStack { @@ -24,6 +26,7 @@ struct ActiveStatusFilterView: View { .font(.system(size: 11).bold()) .padding(.vertical, 5) + /// Deletes the selected filter and removes it from the preview Button(action: { deleteFilterItem(filterItem: "status") self.statusFilterActive.toggle() @@ -41,6 +44,8 @@ struct ActiveStatusFilterView: View { } } + /// Removes the filter from the array with filters + /// - Parameter filterItem: the selected filter you want to remove func deleteFilterItem(filterItem: String) { if let i = filterArr.firstIndex(of: filterItem) { filterArr.remove(at: i) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/FilterView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/FilterView.swift index fffb8620e52ff427e5a49e7f79373d10e062c585..267015dcfb6caa2a56257769a2b21b3b0281c0e7 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/FilterView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/FilterView/FilterView.swift @@ -9,10 +9,13 @@ import SwiftUI // TODO: Add enum for switch case instead of hard-coded values +/// **FilterView** +/// Creates a View displaying the a NavigationView for the different project filters. struct FilterView: View { - // TODO: Add buttons for switching between start before only, period between etc. + /// Filter options @State private var filterItems = ["Område", "Periode", "Størrelse", "Status"] + /// All the different filtervalues @Binding var selStartDateBind: Date @Binding var selEndDateBind: Date @Binding var projectArea: String @@ -20,35 +23,41 @@ struct FilterView: View { @Binding var projectStatus: String @Binding var minProjectSize: Int @Binding var maxProjectSize: Int - // TODO: DENNA TINGEN HER ISTEDENFOR scoreFrom - @Binding var sizeSortType: String + /// Returns whether a filter is active or not @State var periodFilterActive: Bool = false @State var areaFilterActive: Bool = false @State var sizeFilterActive: Bool = false @State var statusFilterActive: Bool = false + /// Array of all the filters active @Binding var filterArr: [String] + + /// All selected areas to be filtered on @Binding var filterArrArea: [String] + /// Selected filter start date and end date @State var selStartDate = Date() @State var selEndDate = Date() var body: some View { NavigationView { List { + /// For each filter type, add it to the List with its respective navigation destination ForEach(filterItems, id: \.self) { filterItem in NavigationLink { switch filterItem { case "Område": FilterProjectArea(selArr: $filterArrArea, areaFilterActive: $areaFilterActive) .onAppear { + /// Resets the filter filterArrArea.removeAll() } case "Periode": FilterProjectPeriod(selStartDateBind: $selStartDate, selEndDateBind: $selEndDate, periodFilterActiveBind: $periodFilterActive) .onAppear { + /// Resets the filter selStartDateBind = Date.distantPast selEndDateBind = Date.distantFuture if (selStartDateBind != Date.distantPast || selEndDateBind != Date.distantFuture) { @@ -75,13 +84,13 @@ struct FilterView: View { FilterProjectStatus(filterArr: $filterArr, selection: $projectStatus) .onChange(of: projectStatus) { status in projectStatus = status - print(projectStatus) statusFilterActive = true } default: Text("No views available") } } label: { + /// Adds label to list as well as adding the filter preview to the list item if it is active HStack { Text(filterItem) Spacer() @@ -132,6 +141,7 @@ struct FilterView: View { .navigationTitle(Text("Filter")) .navigationViewStyle(StackNavigationViewStyle()) .overlay(alignment: .bottom) { + /// Adds and removes active filters based on current selection Button(action: { // TODO: Change to use for loop? /*for filterItem in filterArr { @@ -157,8 +167,6 @@ struct FilterView: View { } else { deleteFilterItem(filterItem: "status") } - - print(filterArr) }) { Text("Bruk") .frame(width: 300, height: 50, alignment: .center) @@ -171,12 +179,17 @@ struct FilterView: View { } } + + /// If the selected filter is newly added and not only updated, add it to the array of filters + /// - Parameter filterItem: the selected filter you want to add func addFilterItem(filterItem: String){ if !filterArr.contains(filterItem) { filterArr.append(filterItem) } } + /// Remove filter item from the array of filters + /// - Parameter filterItem: the selected filter you want to remove func deleteFilterItem(filterItem: String) { if let i = filterArr.firstIndex(of: filterItem) { filterArr.remove(at: i) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/CalendarView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/CalendarView.swift index 11ab17de827f07d62284d420655bba25ec7e52df..a922ae48c29895d6105f5957a3d49789e4610282 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/CalendarView.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/CalendarView.swift @@ -7,11 +7,17 @@ import SwiftUI +/// **CalendarView** +/// Displays the period filter View with two calendars for the user to interact with. struct CalendarView: View { + /// The selected start date and end date @Binding var selStartDate: Date @Binding var selEndDate: Date + + /// Checks if the period filter is active @Binding var periodFilterActive: Bool + /// Base values for the calendar @State private var startDate = Date() @State private var endDate = Date() @@ -21,6 +27,7 @@ struct CalendarView: View { VStack { Section { VStack { + /// First calendar DatePicker( "Start dato", selection: $startDate, @@ -30,6 +37,7 @@ struct CalendarView: View { Divider() + /// Second calendar DatePicker( "Slutt dato", selection: $endDate, @@ -43,14 +51,12 @@ struct CalendarView: View { .padding(.top, 40) } Spacer() + + /// Returns the selected dates to the parent View Button(action: { selStartDate = $startDate.wrappedValue selEndDate = $endDate.wrappedValue periodFilterActive = true - print("______") - print(selStartDate) - print(selEndDate) - print("______") }) { Text("Bruk") .frame(width: 300, height: 50, alignment: .center) diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/FilterProjectPeriod.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/FilterProjectPeriod.swift index fb905174bc0c3826e430137c973aea669dafa20e..31f8debd43c19c1bb8de1d1403e293e6a99f9405 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/FilterProjectPeriod.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/PeriodFilter/FilterProjectPeriod.swift @@ -7,20 +7,27 @@ import SwiftUI +/// **FilterProjectPeriod** +/// The View for selecting a period filter struct FilterProjectPeriod: View { - //@State private var date = Date() + /// Selected start date and end date @Binding var selStartDateBind: Date @Binding var selEndDateBind: Date + + /// Tells if filter is activated or not @Binding var periodFilterActiveBind: Bool @State var periodFilterActive: Bool = true + + /// Initializes the start date and end date to be the date of the day @State private var selStartDate = Date() @State private var selEndDate = Date() - var body: some View { VStack { + /// CalendarView with calendars for both start date and end date CalendarView(selStartDate: $selStartDate, selEndDate: $selEndDate, periodFilterActive: $periodFilterActive) .onAppear { + /// Resets the calendars selected date selStartDateBind = selStartDate selEndDateBind = selEndDate } @@ -34,7 +41,6 @@ struct FilterProjectPeriod: View { } } .navigationTitle(Text("Prosjekt periode")) - //.ignoresSafeArea(edges: .top) } } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeGreaterThanFilter.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeGreaterThanFilter.swift index 3960d4bf85dcff27019a7cbf8e6de8331a8902aa..d108f6965516c925cfac72d8de2912b9753f1faf 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeGreaterThanFilter.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeGreaterThanFilter.swift @@ -7,24 +7,34 @@ import SwiftUI +/// **SizeGreaterThanFilter** +/// The View for selecting a project with size greater than a values struct SizeGreaterThanFilter: View { + + /// Enum for input enum Field: Int, CaseIterable { case input } - + + /// Size filter active @Binding var sizeFilterActive: Bool + /// Has textfield been activated and should be in focus? @FocusState var focusedField: Field? + /// Slider values @State var scoreTo: Int = 1000 @Binding var scoreToBind: Int - + + /// The input of minimum size @ObservedObject var input = NumbersOnly() + /// Slider data var sliderSizeMin = 100.0 var sliderSizeMax = 1000.0 var stepLength = 50.0 + /// Slider value var intProxyS2: Binding<Double>{ Binding<Double>( get: { @@ -43,8 +53,8 @@ struct SizeGreaterThanFilter: View { HStack { VStack { Text("Over") - HStack { + /// Adds textfield with bind to slider TextField("\(Int(sliderSizeMax))", text: $input.value) .font(Font.system(size: 30, design: .default)) .onChange(of: input.value) { value in @@ -92,7 +102,6 @@ struct SizeGreaterThanFilter: View { .font(Font.system(size: 60, design: .default)) VStack { - VStack (alignment: .leading) { HStack { Text("Fra") @@ -102,6 +111,7 @@ struct SizeGreaterThanFilter: View { .font(Font.system(size: 20, design: .default)) .padding(.top, 20) + /// Adds slider for minimum size Slider(value: intProxyS2 , in: sliderSizeMin...sliderSizeMax, step: stepLength, onEditingChanged: {_ in print(scoreTo.description) }) @@ -111,6 +121,8 @@ struct SizeGreaterThanFilter: View { } } Spacer() + + /// Returnerer den brukte størrelsedataen til parent Viewen Button(action: { print("Bruk") scoreTo = Int(input.value) ?? 1000 diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeLessThanFilter.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeLessThanFilter.swift index 2dff321abd2e455ae8c7f6ddd2df73ab7f69f7d6..31fc1b3d7bbe9665fa63c8bd4582bbe65fa41314 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeLessThanFilter.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/BetweenGreaterLess/SizeLessThanFilter.swift @@ -7,23 +7,34 @@ import SwiftUI +/// **SizeLessThanFilter** +/// The View for selecting a project with size less than a values struct SizeLessThanFilter: View { + + /// Enum for input enum Field: Int, CaseIterable { case input } + /// Size filter active @Binding var sizeFilterActive: Bool + /// Has textfield been activated and should be in focus? @FocusState var focusedField: Field? + /// First slider values @State var scoreFrom: Int = 100 @Binding var scoreFromBind: Int + + /// The input of maximum size @ObservedObject var input = NumbersOnly() + /// Slider data var sliderSizeMin = 100.0 var sliderSizeMax = 1000.0 var stepLength = 50.0 + /// Slider value var intProxyS1: Binding<Double>{ Binding<Double>( get: { @@ -42,8 +53,8 @@ struct SizeLessThanFilter: View { HStack { VStack { Text("Under") - HStack { + /// Adds textfield with bind to slider TextField("\(Int(sliderSizeMin))", text: $input.value) .font(Font.system(size: 30, design: .default)) .onChange(of: input.value) { value in @@ -91,6 +102,7 @@ struct SizeLessThanFilter: View { .font(Font.system(size: 20, design: .default)) .padding(.top, 20) + /// Adds slider for maximum size Slider(value: intProxyS1 , in: sliderSizeMin...sliderSizeMax, step: stepLength, onEditingChanged: {_ in print(scoreFrom.description) }) @@ -99,23 +111,25 @@ struct SizeLessThanFilter: View { } } } - Spacer() - Button(action: { - print("Bruk") - scoreFrom = Int(input.value) ?? 100 - scoreFromBind = scoreFrom - sizeFilterActive = true - }) { - Text("Bruk") - .frame(width: 300, height: 50, alignment: .center) + .overlay(alignment: .bottom) { + Spacer() + + /// Returnerer den brukte størrelsedataen til parent Viewen + Button(action: { + print("Bruk") + scoreFrom = Int(input.value) ?? 100 + scoreFromBind = scoreFrom + sizeFilterActive = true + }) { + Text("Bruk") + .frame(width: 300, height: 50, alignment: .center) + } + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(10) + .padding(.bottom, 50) } - .foregroundColor(.white) - //.padding(.vertical, 10) - .background(Color.blue) - .cornerRadius(10) - - Spacer() - .frame(height:50) // limit spacer size by applying a frame + .ignoresSafeArea(.keyboard) } } /* diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/FilterProjectSize.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/FilterProjectSize.swift index 81cd659e47940e17a75991631683192445592143..a0c16563b4106559f028d1b50052de285f09863d 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/FilterProjectSize.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/SizeFilter/FilterProjectSize.swift @@ -7,22 +7,30 @@ import SwiftUI +/// **FilterProjectSize** +/// Responsible for switching between the different filtering Views struct FilterProjectSize: View { + /// Slider values @State var scoreFrom: Int = 100 @State var scoreTo: Int = 1000 + /// Slider selection to be returned to parent View @Binding var scoreFromBind: Int @Binding var scoreToBind: Int + /// Size filter active @Binding var sizeFilterActive: Bool - @Binding var selection: String - let sizeSelections = ["Less Than", "Between", "Greater Than"] + /// The selected size filtering method (Mindre enn, Mellom, Større enn) + @Binding var selection: String + + /// Filtrering metoder + let sizeSelections = ["Mindre enn", "Mellom", "Større enn"] var body: some View { - VStack { VStack { + /// Picker for velging av filtrering metode Picker("Select a state: ", selection: $selection) { ForEach(sizeSelections, id: \.self) { Text($0) @@ -34,13 +42,15 @@ struct FilterProjectSize: View { Spacer() switch selection { - case "Less Than": + case "Mindre enn": + /// Redirects to the SizeLessThanFilter View SizeLessThanFilter(sizeFilterActive: $sizeFilterActive, scoreFrom: scoreFrom, scoreFromBind: $scoreFromBind) .onChange(of: scoreFrom) { val in scoreFromBind = val sizeFilterActive = true } - case "Between": + case "Mellom": + /// Redirects to the SizeBetweenFilter View SizeBetweenFilter(sizeFilterActive: $sizeFilterActive, scoreFrom: scoreFrom, scoreFromBind: $scoreFromBind, scoreTo: scoreTo, scoreToBind: $scoreToBind) .onChange(of: scoreTo) { val in scoreToBind = val @@ -50,7 +60,8 @@ struct FilterProjectSize: View { scoreFromBind = val sizeFilterActive = true } - case "Greater Than": + case "Større enn": + /// Redirects to the SizeGreaterThanFilter View SizeGreaterThanFilter(sizeFilterActive: $sizeFilterActive, scoreTo: scoreTo, scoreToBind: $scoreToBind) .onChange(of: scoreFrom) { val in scoreFromBind = val @@ -71,44 +82,6 @@ extension UIScreen { static let screenSize = UIScreen.main.bounds.size } -struct CornerRadiusStyle: ViewModifier { - var radius: CGFloat - var corners: UIRectCorner - - struct CornerRadiusShape: Shape { - - var radius = CGFloat.infinity - var corners = UIRectCorner.allCorners - - func path(in rect: CGRect) -> Path { - let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) - return Path(path.cgPath) - } - } - - func body(content: Content) -> some View { - content - .clipShape(CornerRadiusShape(radius: radius, corners: corners)) - } -} - -extension View { - func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { - ModifiedContent(content: self, modifier: CornerRadiusStyle(radius: radius, corners: corners)) - } -} - -class NumbersOnly: ObservableObject { - @Published var value = "" { - didSet { - let filtered = value.filter { $0.isNumber } - - if value != filtered { - value = filtered - } - } - } -} /* struct FilterProjectSize_Previews: PreviewProvider { static var previews: some View { diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/StatusFilter/FilterProjectStatus.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/StatusFilter/FilterProjectStatus.swift index 95b182ea262bc3d9e1a87baaea97c91656bf548a..7166c5bba0d6661bdfb85e7ef04bfca5d47f30d9 100644 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/StatusFilter/FilterProjectStatus.swift +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/FilterData/StatusFilter/FilterProjectStatus.swift @@ -7,49 +7,57 @@ import SwiftUI +/// **FilterProjectStatus** +/// The View for selecting a status filter struct FilterProjectStatus: View { + /// Array of all the filters active @Binding var filterArr: [String] + + /// The selected filter @Binding var selection: String - let states = ["Inactive", "Active", "Upcomming"] + /// The different project states + let states = ["Inactive", "Active", "Upcomming"] - var body: some View { - VStack { - Text("Select project status") - .font(.headline) - .padding(.vertical, 5) - - Picker("Select a state: ", selection: $selection) { - ForEach(states, id: \.self) { - Text($0) - } + var body: some View { + VStack { + Text("Select project status") + .font(.headline) + .padding(.vertical, 5) + + /// Picker for choosing a project state + Picker("Select a state: ", selection: $selection) { + ForEach(states, id: \.self) { + Text($0) } - .pickerStyle(SegmentedPickerStyle()) - - HStack { - Text("Selected state: ") - + - Text("\(selection)") - .bold() - - } - .padding(.vertical, 10) + } + .pickerStyle(SegmentedPickerStyle()) + + HStack { + Text("Selected state: ") + + + Text("\(selection)") + .bold() + + } + .padding(.vertical, 10) - Spacer() + Spacer() + } + .navigationTitle(Text("Status")) + .overlay(alignment: .bottom) { + /// Adds the selection and adds filter to filter array + Button(action: { + selection = selection + filterArr.append("status") + }) { + Text("Bruk") + .frame(width: 300, height: 50, alignment: .center) } - .navigationTitle(Text("Status")) - .overlay(alignment: .bottom) { - Button(action: { - selection = selection - filterArr.append("status") - }) { - Text("Bruk") - .frame(width: 300, height: 50, alignment: .center) - } - .foregroundColor(.white) - .background(Color.blue) - .cornerRadius(10) - .padding(.bottom, 50) + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(10) + .padding(.bottom, 50) } } } diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectDetailView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectDetailView.swift deleted file mode 100644 index 5dcb8d6e278a97be870e0100a515f972a9bb76c5..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectDetailView.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// ProjectDetailView.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 04/04/2022. -// - -import SwiftUI -import MapKit - -struct ProjectDetailView: View { - @Environment(\.colorScheme) var colorScheme - @State private var isShowingSheet = false - - var project: Project - - var body: some View { - ScrollView { - VStack { - /// MapView displaying the map in the top of the screen - MapView() - .frame(height: 300) - - Text("\(project.projectName)") - .font(.title).bold() - .foregroundColor(colorScheme == .dark ? Color(UIColor.darkGray) : Color(UIColor.darkGray)) - - DetailView(project: project) - - Button { - isShowingSheet.toggle() - } label: { - Text("Transfere Scaffolding") - .padding(12) - .font(.system(size: 20)) - .foregroundColor(colorScheme == .dark ? Color(UIColor.black) : Color(UIColor.darkGray)) - } - .contentShape(Rectangle()) - .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) - .sheet(isPresented: $isShowingSheet, - onDismiss: didDismiss) { - TransfereScaffolding() - } - } - } - .ignoresSafeArea(edges: .top) - } - func didDismiss() { - // Handle the dismissing action. - } -} - -struct DetailView: View { - var project: Project - - @Environment(\.colorScheme) var colorScheme - - var body: some View { - let projectInfoTitle = "Project Information" - let duration = "Duration:" - let customer = "Customer:" - let amountScaff = "Amount:" - - VStack { - VStack(alignment: .leading) { - Text(projectInfoTitle) - .font(.title).bold() - - HStack { - Text(duration) - .font(.body).bold() - - Text("\(project.period.startDate) - \(project.period.endDate)") - .font(.body) - } - - HStack { - Text(customer) - .font(.body).bold() - - Text("\(project.customer.name)") - .font(.body) - } - - HStack { - Text(amountScaff) - .font(.body).bold() - - Text("\("ADD INFO TO API")") - .font(.body) - } - } - .foregroundColor(Color(UIColor.darkGray)) - .lineLimit(1) - .layoutPriority(100) - .frame(width: 350, height: 125) - .background(colorScheme == .dark ? Color.white : Color(UIColor.white)) - .cornerRadius(15) - .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) - .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) - .overlay( - RoundedRectangle(cornerRadius: 15) - .stroke(colorScheme == .dark ? Color.gray.opacity(0.1) : Color.gray.opacity(0.1), lineWidth: 1) - ) - } - } -} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView.swift deleted file mode 100644 index d469dbcaa959e24d811252751036a8a2cea16681..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView.swift +++ /dev/null @@ -1,230 +0,0 @@ -// -// ProjectView.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 28/03/2022. -// - -import SwiftUI -import UIKit -/* -struct BookMark: Identifiable { - let id = UUID() - let name: String - let icon: String - var items: [BookMark]? -} - -struct ProjectView1: View { - let items: [BookMark] = [.example1, .example2, .example3] - var body: some View { - Section(header: Text("Second List")) { - List(items, children: \.items) { row in - Image(systemName: row.icon) - Text(row.name) - } - } - } -}*/ - - -enum FilterType { - case none, - period, - startBeforePeriod, - startAfterPeriod, - endBeforePeriod, - endAfterPeriod, - sizeBetween, - sizeEqualTo, - sizeLessThan, - sizeGreaterThan, - state, - county -} - -struct ProjectRow: View { - var project: Project - - var body: some View { - HStack { - VStack(alignment: .leading) { - Text(project.projectName).font(.headline) - Text(project.period.startDate + " until " + project.period.endDate).font(.subheadline).foregroundColor(.gray) - } - Spacer() - Text(String(format: "%d", project.projectID)) - .foregroundColor(.gray) - } - } -} - -/* -extension BookMark { - static let apple = BookMark(name: "Apple", icon: "1.circle") - static let bbc = BookMark(name: "BBC", icon: "square.and.pencil") - static let swift = BookMark(name: "Swfit", icon: "bolt.fill") - static let twitter = BookMark(name: "Twitter", icon: "mic") - - static let example1 = BookMark(name: "Favorites", icon: "star", items: [BookMark.apple, BookMark.bbc, BookMark.swift, BookMark.twitter]) - static let example2 = BookMark(name: "Recent", icon: "timer", items: [BookMark.apple, BookMark.bbc, BookMark.swift, BookMark.twitter]) - static let example3 = BookMark(name: "Recommended", icon: "hand.thumbsup", items: [BookMark.apple, BookMark.bbc, BookMark.swift, BookMark.twitter]) -} -*/ - -struct ProjectView: View { - @State var searchQuery = "" - @State var hasFetchedData = false - @State var projects = [Project]() - @State private var showFilterModalView: Bool = false - - @State var sizeSortType: String = "Between" - @State var filter: FilterType = .none - @State var filterArr: [String] = [] - @State var filterArrArea: [String] = [] - - // TODO: REMOVE? - @State var projectStartDate = Date.distantPast - @State var projectEndDate = Date.distantFuture - @State var projectSize = 99999 - @State var minProjectSize = 100 - @State var maxProjectSize = 1000 - @State var projectState = "Active" - @State var projectCounty = "Innlandet" - - var body: some View { - VStack { - NavigationView { - Form { - Section(header: Text("All Projects")) { - /*List(searchResults, id: \.projectID) { project in - NavigationLink(destination: ProjectDetailView(project: project), label: { - ProjectRow(project: project) } - ) - } - .navigationTitle("Projects")*/ - List(searchResults, id: \.projectID) { project in - NavigationLink(destination: ProjectDetailView(project: project), label: { - ProjectRow(project: project) } - ) - } - .navigationTitle("Projects") - //.listStyle(.grouped) - } - } - .listStyle(.grouped) - .toolbar { - ToolbarItemGroup(placement: .navigationBarLeading) { - Button(action: { - print("Filter tapped!") - self.showFilterModalView.toggle() - }) { - Label("Filter", systemImage: "line.3.horizontal.decrease.circle") - } - } - - ToolbarItemGroup(placement: .navigationBarTrailing) { - Button(action: { - print("Add project tapped!") - }) { - Label("Add", systemImage: "plus.circle") - } - } - } - } - .task { - await ProjectData().loadData { (projects) in - self.projects = projects - } - } - .sheet(isPresented: $showFilterModalView, - onDismiss: didDismiss) { - FilterView(selStartDateBind: $projectStartDate, selEndDateBind: $projectEndDate, projectArea: $projectCounty, projectSize: $projectSize, projectStatus: $projectState, minProjectSize: $minProjectSize, maxProjectSize: $maxProjectSize, sizeSortType: $sizeSortType, filterArr: $filterArr, filterArrArea: $filterArrArea) - .onChange(of: filterArr) { filterVal in - if filterArr.contains("period") { - filter = .period - } - if filterArr.contains("area") { - filter = .county - } - if filterArr.contains("size") && sizeSortType == "Between" { - filter = .sizeBetween - } else if filterArr.contains("size") && sizeSortType == "Less Than" { - filter = .sizeLessThan - } else if filterArr.contains("size") && sizeSortType == "Greater Than" { - filter = .sizeGreaterThan - } - if filterArr.contains("status") { - filter = .state - } - if filterArr.isEmpty { - filter = .none - } - } - } - .navigationViewStyle(.stack) - .searchable(text: $searchQuery, placement: .navigationBarDrawer(displayMode: .always)) - .refreshable { - print("Refreshed") - // TODO: Add action here (retrieve from API again or something) - } - //ProjectView1() - } - } - - // TODO: Add support for search of filtered items - var searchResults: [Project] { - if searchQuery.isEmpty { - return filteredProjects.sorted { $0.projectName < $1.projectName } - } else { - return filteredProjects.filter { $0.projectName.lowercased().contains(searchQuery.lowercased()) }.sorted { $0.projectName < $1.projectName } - } - } - - func didDismiss() { - // Handle the dismissing action. - } - - var filteredProjects: [Project] { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "dd/MM/yyyy" - - switch filter { - case .none: - return projects - case .period: - //return projects.filter { $0.period.startDate > projectStartDate && $0.period.endDate < projectEndDate } - return projects.filter { dateFormatter.date(from: $0.period.startDate)! >= projectStartDate && dateFormatter.date(from: $0.period.endDate)! <= projectEndDate } - case .startBeforePeriod: - //return projects.filter { $0.period.startDate < projectStartDate } - return projects.filter { dateFormatter.date(from: $0.period.startDate)! <= projectStartDate } - case .startAfterPeriod: - //return projects.filter { $0.period.startDate > projectStartDate } - return projects.filter { dateFormatter.date(from: $0.period.startDate)! >= projectStartDate } - case .endBeforePeriod: - //return projects.filter { $0.period.endDate < projectEndDate } - return projects.filter { dateFormatter.date(from: $0.period.endDate)! <= projectEndDate } - case .endAfterPeriod: - //return projects.filter { $0.period.endDate > projectEndDate } - return projects.filter { dateFormatter.date(from: $0.period.endDate)! >= projectEndDate } - case .sizeBetween: - return projects.filter { $0.size >= Int(minProjectSize) && $0.size <= Int(maxProjectSize)} - case .sizeEqualTo: - return projects.filter { $0.size == Int(projectSize) } - case .sizeLessThan: - return projects.filter { $0.size < Int(minProjectSize) } - case .sizeGreaterThan: - return projects.filter { $0.size > Int(maxProjectSize) } - case .state: - return projects.filter { $0.state == projectState } - case .county: - return projects.filter { filterArrArea.contains($0.address.county)} - } - } -} - -struct Project_Previews: PreviewProvider { - static var previews: some View { - ProjectView() - } -} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/HistoryOfScaffolding.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/HistoryOfScaffolding.swift new file mode 100644 index 0000000000000000000000000000000000000000..4b4b982501a8c8459f09f2f0c0c6f5b00af9d744 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/HistoryOfScaffolding.swift @@ -0,0 +1,232 @@ +// +// HistoryOfScaffolding.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 09/05/2022. +// + +import SwiftUI + +/// **HistoryOfScaffolding** +/// A View for showing the history of a scaffolding type for a project +/// As of now, it is hard coded due to missing implementation in the API, however, the design and concept is the same. +struct HistoryOfScaffolding: View { + /// All projects + var projects: [Project] + + /// Darkmode or lightmode activated? + @Environment(\.colorScheme) var colorScheme + + /// Scaffolding type + var scaffolding: Scaffolding + + /// Transfere scaffolding Modal View showing + @Binding var isShowingSheet: Bool + + /// Used to show dates in history + let dateNow = Date() + + var body: some View { + ScrollView(.vertical) { + VStack (alignment: .leading){ + HStack { + VStack { + /// Each "blob" with history info + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .frame(width: UIScreen.screenWidth / 2.3 , height: 60) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: -2, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: -10, y: 10) + .overlay(VStack { + /// Preview data for scaffolding on the project on a given date + Text(Date(), style: .date) + .foregroundColor(Color.gray) + HStack { + VStack { + Text(String(format: "%d", scaffolding.quantity.expected)) + .font(.system(size: 15)) + Text("FORVENTET") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + VStack { + /// Changes color of registered depending on how close it is to be equal to expected + amountOfScaffoldingRegistered(expected: scaffolding.quantity.expected, registered: scaffolding.quantity.registered) + Text("REGISTRERT") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + } + }) + .padding(.leading) + .padding(.vertical, 35) + .ignoresSafeArea() + + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .frame(width: UIScreen.screenWidth / 2.3 , height: 60) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: -2, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: -10, y: 10) + .overlay(VStack { + /// Sets date to go back in time for each history "blob" + Text(Calendar.current.date(byAdding: .day, value: -2, to: dateNow)!, style: .date) + .foregroundColor(Color.gray) + HStack { + VStack { + Text(String(format: "%d", scaffolding.quantity.expected)) + .font(.system(size: 15)) + Text("FORVENTET") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + VStack { + amountOfScaffoldingRegistered(expected: scaffolding.quantity.expected, registered: scaffolding.quantity.registered) + Text("REGISTRERT") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + } + }) + .padding(.leading) + .padding(.vertical, 35) + .ignoresSafeArea() + + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .frame(width: UIScreen.screenWidth / 2.3 , height: 60) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: -2, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: -10, y: 10) + .overlay(VStack { + Text(Calendar.current.date(byAdding: .day, value: -4, to: dateNow)!, style: .date) + .foregroundColor(Color.gray) + HStack { + VStack { + Text(String(format: "%d", scaffolding.quantity.expected)) + .font(.system(size: 15)) + Text("FORVENTET") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + VStack { + amountOfScaffoldingRegistered(expected: scaffolding.quantity.expected, registered: scaffolding.quantity.registered) + Text("REGISTRERT") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + } + }) + .padding(.leading) + .padding(.vertical, 35) + .ignoresSafeArea() + } + + Divider() + .frame(width: 3, height: 400, alignment: .center) + .background(Color.gray) + .padding(.top) + .offset(y: 30) + + VStack { + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .frame(width: UIScreen.screenWidth / 2.3 , height: 60) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 2, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 10, y: 10) + .overlay(VStack { + Text(Calendar.current.date(byAdding: .day, value: -1, to: dateNow)!, style: .date) + .foregroundColor(Color.gray) + HStack { + VStack { + Text(String(format: "%d", scaffolding.quantity.expected)) + .font(.system(size: 15)) + Text("FORVENTET") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + VStack { + amountOfScaffoldingRegistered(expected: scaffolding.quantity.expected, registered: scaffolding.quantity.registered) + .font(.system(size: 15)) + Text("REGISTRERT") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + } + }) + .offset(y: 70) + .padding(.trailing) + .padding(.vertical, 35) + .ignoresSafeArea() + + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .frame(width: UIScreen.screenWidth / 2.3 , height: 60) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 2, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 10, y: 10) + .overlay(VStack { + Text(Calendar.current.date(byAdding: .day, value: -3, to: dateNow)!, style: .date) + .foregroundColor(Color.gray) + HStack { + VStack { + Text(String(format: "%d", scaffolding.quantity.expected)) + .font(.system(size: 15)) + Text("FORVENTET") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + VStack { + amountOfScaffoldingRegistered(expected: scaffolding.quantity.expected, registered: scaffolding.quantity.registered) + .font(.system(size: 15)) + Text("REGISTRERT") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + } + }) + .offset(y: 70) + .padding(.trailing) + .padding(.vertical, 35) + .ignoresSafeArea() + + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .frame(width: UIScreen.screenWidth / 2.3 , height: 60) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 2, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 10, y: 10) + .overlay(VStack { + Text(Calendar.current.date(byAdding: .day, value: -5, to: dateNow)!, style: .date) + .foregroundColor(Color.gray) + HStack { + VStack { + Text(String(format: "%d", scaffolding.quantity.expected)) + .font(.system(size: 15)) + Text("FORVENTET") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + VStack { + amountOfScaffoldingRegistered(expected: scaffolding.quantity.expected, registered: scaffolding.quantity.registered) + .font(.system(size: 15)) + Text("REGISTRERT") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + } + }) + .offset(y: 70) + .padding(.trailing) + .padding(.vertical, 35) + .ignoresSafeArea() + } + } + } + } + .navigationTitle(Text("Historie for \(scaffolding.type)".capitalizingFirstLetter())) + } +} + +/* +struct HistoryOfScaffolding_Previews: PreviewProvider { + static var previews: some View { + HistoryOfScaffolding() + } +}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoDetailedView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoDetailedView.swift new file mode 100644 index 0000000000000000000000000000000000000000..30cab034ff95435faea6c1cf5ffbfa3507ae64c2 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoDetailedView.swift @@ -0,0 +1,179 @@ +// +// ProjectInfoDetailedView.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 29/04/2022. +// + +import SwiftUI +import UIKit + +struct ProjectInfoDetailedView: View { + /// Selected project + var project: Project + + /// Darkmode or lightmode? + @Environment(\.colorScheme) var colorScheme + + /// Predefined to be easily maintianed and changed + let duration = "VARIGHET" + let customer = "KUNDE" + let amountScaff = "MENGDE" + let scaffoldingSize = "STØRRELSE" + let state = "STATUS" + let address = "ADRESSE" + let contactPerson = "KONTAKT PERSON" + let phoneNumber = "MOBILNUMMER" + let email = "EMAIL" + + var body: some View { + VStack { + VStack(alignment: .leading) { + /// Project info card + VStack(alignment: .leading) { + VStack { + VStack { + Image(systemName: "square.text.square") + .resizable() + .frame(width: 30, height: 30) + .foregroundColor(.blue) + + Text("Prosjekt info") + .font(Font.system(size: 20).bold()) + .padding(.bottom, 2) + + Text("Nedenfor finner du informasjon om dette prosjektet.") + .font(.caption) + .foregroundColor(Color.gray) + .padding(.bottom, 5) + } + + VStack { + Text("\(project.customer.name)") + .font(.body) + + Text(customer) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack(alignment: .leading) { + VStack { + Text("\(project.period.startDate) - \(project.period.endDate)") + .font(.body) + + Text(duration) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + } + .padding(.bottom, 5) + + VStack { + HStack { + Text("\(project.size) m") + + Text("2") + .baselineOffset(6) + .font(.system(size: 12)) + } + .font(.body) + + Text(scaffoldingSize) + .foregroundColor(.gray) + .font(.system(size: 15)) + .padding(.bottom, 5) + } + + VStack { + Text("\(project.state)") + .font(.body) + + Text(state) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + } + } + .padding() + .frame(width: (UIScreen.screenWidth / 1.2), alignment: .center) + .contentShape(RoundedRectangle(cornerRadius: 5)) + .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) + + /// Contact info card + VStack { + VStack { + Image(systemName: "person.circle") + .resizable() + .frame(width: 30, height: 30) + .foregroundColor(.blue) + + Text("Kontakt info") + .font(Font.system(size: 20).bold()) + .padding(.bottom, 2) + + Text("Nedenfor finner du kontaktinformasjonen til kunden.") + .font(.caption) + .foregroundColor(Color.gray) + .padding(.bottom, 5) + } + + VStack { + Text("\(project.customer.name)") + .font(.body) + + Text(contactPerson) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(project.address.street), \(project.address.zipcode) \(project.address.municipality)") + .font(.body) + + Text(address) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(project.customer.number)") + .font(.body) + + Text(phoneNumber) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + .padding(.bottom, 5) + + VStack { + Text("\(project.customer.email)") + .font(.body) + + Text(email) + .foregroundColor(.gray) + .font(.system(size: 15)) + } + } + .padding() + .frame(width: (UIScreen.screenWidth / 1.2), alignment: .center) + .contentShape(RoundedRectangle(cornerRadius: 5)) + .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) + } + } + .padding(.horizontal, 20) + } +} + +/* +struct ProjectInfoDetailedView_Previews: PreviewProvider { + static var previews: some View { + ProjectInfoDetailedView() + } +}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoView.swift new file mode 100644 index 0000000000000000000000000000000000000000..da4da0f60dbee413d612eef8b6c01e739f01d427 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ProjectInfoView.swift @@ -0,0 +1,71 @@ +// +// ProjectDetailView.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 04/04/2022. +// + +import SwiftUI +import MapKit + +/// **ProjectInfoView** +/// The View responsible for displaying the project info and scaffolding info Views +struct ProjectInfoView: View { + /// Darkmode or light mode? + @Environment(\.colorScheme) var colorScheme + + /// Transfere scaffolding Modal View showing? + @State private var isShowingSheet = false + + /// All projects + var projects: [Project] + + /// Specific project + var project: Project + + /// The two views available + let siteSelections = ["Stillas", "Prosjekt Info"] + + /// Initialize selection + @State var selection: String = "Prosjekt Info" + + var body: some View { + ScrollView { + VStack { + /// MapView displaying the map in the top of the screen + MapView() + .frame(height: 300) + + Text("\(project.projectName)") + .font(.title).bold() + .foregroundColor(colorScheme == .dark ? Color(UIColor.darkGray) : Color(UIColor.darkGray)) + + VStack { + Picker("Select a state: ", selection: $selection) { + ForEach(siteSelections, id: \.self) { + Text($0) + } + } + .pickerStyle(SegmentedPickerStyle()) + .padding(.bottom, 15) + + Spacer() + + switch selection { + case "Prosjekt Info": + VStack { + ProjectInfoDetailedView(project: project) + } + case "Stillas": + VStack { + ScaffoldingView(projects: projects, scaffolding: project.scaffolding!) + } + default: + Text("Found none") + } + } + } + } + .ignoresSafeArea(edges: .top) + } +} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingDetailedView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingDetailedView.swift new file mode 100644 index 0000000000000000000000000000000000000000..954324068013f0def84efe2cf85702df7797c97f --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingDetailedView.swift @@ -0,0 +1,39 @@ +// +// ScaffoldingDetailedView.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 09/05/2022. +// + +import SwiftUI + +/// **ScaffoldingDetailedView** +/// The detailed view of each scaffolding type +/// Snowes history of the type as well as a button which redirects to transfering of scaffolding +struct ScaffoldingDetailedView: View { + + /// All projects used in transfere scaffolding + var projects: [Project] + + /// The scaffolding type + var scaffolding: Scaffolding + + /// Transfere scaffolding Modal View is showing + @Binding var isShowingSheet: Bool + + var body: some View { + VStack { + /// History of scaffolding unit type for project and transfere scaffolding button + HistoryOfScaffolding(projects: projects, scaffolding: scaffolding, isShowingSheet: $isShowingSheet) + + TransfereScaffoldingButton(projects: projects, scaffolding: scaffolding, isShowingSheet: $isShowingSheet) + } + } +} + +/* +struct ScaffoldingDetailedView_Previews: PreviewProvider { + static var previews: some View { + ScaffoldingDetailedView() + } +}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItem.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItem.swift new file mode 100644 index 0000000000000000000000000000000000000000..e782b36c4ee19fc1d3ad636792b7ed3603b18ef8 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItem.swift @@ -0,0 +1,101 @@ +// +// ScaffoldingItem.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 01/05/2022. +// + +import SwiftUI + +/// **ScaffoldingItem** +/// The preview used for the scaffolding buttons with scaffolding preview data +struct ScaffoldingItem: View { + /// Checks if the device has light- or dark mode activated + @Environment(\.colorScheme) var colorScheme + + /// The scaffolding unit + var scaffolding: Scaffolding + + var body: some View { + VStack { + /// Preview data for the button in use + Text("\(scaffolding.type)".capitalizingFirstLetter()).font(.title2) + .lineLimit(1) + + Image("\(scaffolding.type)".capitalizingFirstLetter()).resizable().scaledToFit() + + Spacer() + + HStack { + VStack { + Text(String(format: "%d", scaffolding.quantity.expected)).foregroundColor(.black) + .font(.system(size: 15)) + Text("FORVENTET") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + .frame(alignment: .center) + + VStack { + amountOfScaffoldingRegistered(expected: scaffolding.quantity.expected, registered: scaffolding.quantity.registered) + Text("REGISTRERT") + .foregroundColor(.gray) + .font(.system(size: 10)) + } + .frame(alignment: .center) + } + } + .padding(.vertical, 5) + .frame(width: (UIScreen.screenWidth / 2) - 40, height: (UIScreen.screenWidth / 2) - 40, alignment: .center) + .contentShape(RoundedRectangle(cornerRadius: 5)) + .background(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)).cornerRadius(7) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) + } +} + + +/// A method for checking the registered scaffolding up against the expected scaffolding and assigning color to the result text depending on how close they are to be equal to eachother +/// - Parameters: +/// - expected: The expected amount of the scaffolding type for the project +/// - registered: The registered amount of the scaffolding type for the project at a given time +/// - Returns: The text of registered units with either red, yellow, green or purple color depending on count +func amountOfScaffoldingRegistered(expected: Int, registered: Int) -> Text { + if (registered >= Int(Double(expected) * 0.95) && registered <= Int(Double(expected))) { + return Text(String(format: "%d", registered)).foregroundColor(Color.green) + .font(.system(size: 15)) + } else if ((registered < Int(Double(expected) * 0.95)) && (registered >= Int(Double(expected) * 0.8))) { + return Text(String(format: "%d", registered)).foregroundColor(Color.yellow) + .font(.system(size: 15)) + } else if (registered > Int(Double(expected))) { + return Text(String(format: "%d", registered)).foregroundColor(Color.purple) + .font(.system(size: 15)) + } else { + return Text(String(format: "%d", registered)).foregroundColor(Color.red) + .font(.system(size: 15)) + } +} + +/// An extension to the String struct which adds a function for capitalizing the first letter in a word. +/// This was taken in use from the following URL: +/// https://www.hackingwithswift.com/example-code/strings/how-to-capitalize-the-first-letter-of-a-string +extension String { + + /// Capitalizes the first letter of a word + /// - Returns: The word with the capitalized first letter + func capitalizingFirstLetter() -> String { + return prefix(1).capitalized + dropFirst() + } + + mutating func capitalizeFirstLetter() { + self = self.capitalizingFirstLetter() + } +} + +/* +struct ScaffoldingItem_Previews: PreviewProvider { + static var previews: some View { + ScaffoldingItem() + } +} +*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItems.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItems.swift new file mode 100644 index 0000000000000000000000000000000000000000..cd420009dcb87a83e6346d9fafabd91b22fd3c63 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingItems.swift @@ -0,0 +1,46 @@ +// +// ScaffoldingItems.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 01/05/2022. +// + +import SwiftUI + +/// **ScaffoldingItems** +/// A grid containing all the scaffolding items as buttons +struct ScaffoldingItems: View { + /// Projects needed for transfering scaffolding + var projects: [Project] + + /// A two-column grid layout which is flexible + var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible())] + + /// All scaffolding items + var scaffolding: [Scaffolding] + + /// Modal View of transfere scaffolding + @Binding var isShowingSheet: Bool + + var body: some View { + ScrollView (.vertical) { + LazyVGrid (columns: gridItemLayout, spacing: 10) { + /// For each scaffolding type on the project, add scaffolding button + ForEach(scaffolding, id: \.type) { scaffolding in + NavigationLink(destination: ScaffoldingDetailedView(projects: projects, scaffolding: scaffolding, isShowingSheet: $isShowingSheet), + label: { ScaffoldingItem(scaffolding: scaffolding) + }) + .listStyle(.grouped) + } + } + .padding(.vertical, 20) + } + } +} + +/* +struct ScaffoldingItems_Previews: PreviewProvider { + static var previews: some View { + ScaffoldingItems() + } +}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingView.swift new file mode 100644 index 0000000000000000000000000000000000000000..9f4fa8b709ebb451dd4c1c6e2ba7f6d9a68f2cbd --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/ScaffoldingView.swift @@ -0,0 +1,32 @@ +// +// ScaffoldingView.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 29/04/2022. +// + +import SwiftUI + +/// **ScaffoldingView** +/// The view responsible for showing scaffolding units +struct ScaffoldingView: View { + var projects: [Project] + + /// All scaffolding units + var scaffolding: [Scaffolding] + + /// Scaffolding item Modal View + @State var isShowingSheet: Bool = false + var body: some View { + VStack { + ScaffoldingItems(projects: projects, scaffolding: scaffolding, isShowingSheet: $isShowingSheet) + } + } +} + +/* +struct ScaffoldingView_Previews: PreviewProvider { + static var previews: some View { + ScaffoldingView() + } +}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffolding.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffolding.swift new file mode 100644 index 0000000000000000000000000000000000000000..813f99cd8af7cdbd6c1ac9af27d2459c6cc23e40 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffolding.swift @@ -0,0 +1,45 @@ +// +// TransfereScaffolding.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 05/04/2022. +// + +import SwiftUI +import Foundation + +/// **TrandsfereScaffolding** +/// Background view of TransfereScaffolding +struct TransfereScaffolding: View { + /// All projects used for transfering + var projects: [Project] + + /// Lightmode or darkmode? + @Environment(\.colorScheme) var colorScheme + + /// Scaffolding type + var scaffolding: Scaffolding + + /// Transfere Modal View is showing + @Binding var isShowingSheet: Bool + + var body: some View { + VStack { + /// Transfere scaffolding view + TransfereScaffoldingView(isShowingSheet: $isShowingSheet, projects: projects, scaffolding: scaffolding) + .navigationTitle(Text("Overfør \(scaffolding.type)")) + } + } + + /// Modal view dismissed + func didDismiss() { + // Handle the dismissing action. + } +} + +/* +struct TransfereScaffolding_Previews: PreviewProvider { + static var previews: some View { + TransfereScaffolding() + } +}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingButton.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingButton.swift new file mode 100644 index 0000000000000000000000000000000000000000..1a061eb4865d103182abf2b0dfecee58bd7e86ef --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingButton.swift @@ -0,0 +1,64 @@ +// +// TransfereScaffoldingButton.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 01/05/2022. +// + +import SwiftUI + +/// **TransfereScaffoldingButton** +/// Button for activating of transfere scaffolding Modal View +struct TransfereScaffoldingButton: View { + /// All projects + var projects: [Project] + + /// Darkmode or lightmode activated? + @Environment(\.colorScheme) var colorScheme + + /// Specific scaffolding type + var scaffolding: Scaffolding + + /// Transfere scaffolding Modal View is showing + @Binding var isShowingSheet: Bool + + var body: some View { + /// Button for opening transfere scaffolding Modal View + Button { + isShowingSheet.toggle() + } label: { + Text("Overfør Stillas") + .padding(12) + .font(.system(size: 20)) + .foregroundColor(colorScheme == .dark ? Color(UIColor.white) : Color(UIColor.white)) + .frame(width: 300, height: 50, alignment: .center) + } + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(10) + .padding(.bottom, 50) + + .contentShape(Rectangle()) + //.background(colorScheme == .dark ? Color.blue : Color.blue).cornerRadius(7) + .shadow(color: Color(UIColor.black).opacity(0.1), radius: 5, x: 0, y: 2) + .shadow(color: Color(UIColor.black).opacity(0.2), radius: 20, x: 0, y: 10) + .sheet(isPresented: $isShowingSheet, + onDismiss: didDismiss) { + /// Transfere scaffolding Modal View + TransfereScaffolding(projects: projects, scaffolding: scaffolding, isShowingSheet: $isShowingSheet) + } + } + + /// Modal view got dismissed + func didDismiss() { + // Handle the dismissing action. + } +} + +/* +struct TransfereScaffoldingButton_Previews: PreviewProvider { + static var previews: some View { + TransfereScaffoldingButton() + } +} +*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingView.swift new file mode 100644 index 0000000000000000000000000000000000000000..49d8aff8bbcf741a3f6a90443c1df936f8d32df7 --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectDetailView/TransfereScaffoldingView.swift @@ -0,0 +1,291 @@ +// +// TransfereScaffoldingView.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 09/05/2022. +// + +import SwiftUI +import Combine + +/// **TransfereScaffoldingView** +/// The View for transfering scaffolding +struct TransfereScaffoldingView: View { + /// Input field + enum Field: Int, CaseIterable { + case input + } + + /// Is Modal View active + @Binding var isShowingSheet: Bool + + /// Field is selected --> Focus + @FocusState var focusedField: Field? + + /// All projects + var projects: [Project] + + /// Scaffolding type + var scaffolding: Scaffolding + + /// Value of project picker selection + @State private var pickerSelectionFrom: String = "" + @State private var pickerSelectionTo: String = "" + + /// Checks if "from project" is not selected yet + @State private var pickerSelectionFromNotPicked: Bool = true + + /// Search for projects + @State private var searchTerm: String = "" + + /// Is amount of scaffolding empty? + @State private var empty = false + + /// The number of scaffolding (as string because of textfield) + @State var anumber: String = "" + + /// Template numbers to be picked from + var commonNumbers: [String] = ["1", "5", "10", "25", "50"] + + @State var selectedIndex: Int = -1 + @State private var confirmationMessage = "" + @State private var confirmationTitle = "" + @State private var showingConfirmation = false + @State private var transfereMessage = "" + @State private var transfereConfirmation: Bool = false + + /// ProjectID's + @State var pickedFromID: Int = 0 + @State var pickedToID: Int = 0 + + @State var tempIntFrom: Int = 0 + @State var tempIntTo: Int = 0 + + /// Checks the expected count of the selected project + @State var tem: Int = 0 + + /// The filtered projects + var filteredProjects: [Project] { + projects.filter { + searchTerm.isEmpty ? true : $0.projectName.lowercased().contains(searchTerm.lowercased()) + } + } + + var body: some View { + NavigationView { + Form { + Section(header: Text("Fra prosjekt")) { + /// Picker for the project to transfere scaffolding from + Picker(selection: $pickerSelectionFrom, label: Text("Fra prosjekt")) { + SearchBar(text: $searchTerm, placeholder: "Søk etter prosjekt") + .onAppear { + searchTerm = "" + /// Reset number of scaffolding + anumber.removeAll() + } + .onChange(of: pickerSelectionFrom) { pick in + tempIntFrom = projects.firstIndex(where: { $0.projectName == pick })! + pickedFromID = projects[tempIntFrom].projectID + pickerSelectionFromNotPicked = false + } + /// List all projects + ForEach(filteredProjects, id: \.projectID) { project in + Text("\(project.projectName)").tag("\(project.projectName)") + } + } + .onAppear{ + if pickerSelectionFrom.isEmpty { + // TODO: Fix border and error handling here + } + } + } + + Section(header: Text("Til prosjekt")) { + /// Picker for the project to transfere scaffolding to + Picker(selection: $pickerSelectionTo, label: Text("Til prosjekt")) { + SearchBar(text: $searchTerm, placeholder: "Søk etter prosjekt") + .onAppear { + searchTerm = "" + } + .onChange(of: pickerSelectionTo) { pick in + tempIntTo = projects.firstIndex(where: { $0.projectName == pick })! + pickedToID = projects[tempIntTo].projectID + } + /// List all projects + ForEach(filteredProjects, id: \.projectID) { project in + Text("\(project.projectName)").tag("\(project.projectName)") + } + } + } + + Section (header: Text("Antall \(scaffolding.type)")){ + VStack(alignment: .leading) { + Text("Forhåndsdefinert mengde") + /// Picker for the predefined scaffolding amount + Picker("Pick a number", selection: $anumber) { + ForEach(commonNumbers, id: \.self) { aNumber in + Image(systemName: "\(aNumber).circle.fill") + } + } + .pickerStyle(SegmentedPickerStyle()) + } + .padding() + + HStack { + Text("Manuell innfylling") + TextField("Input", text: $anumber) + .disabled(pickerSelectionFromNotPicked) + //.modifier(ClearButton(text: $anumber)) + .onChange(of: anumber) { + tem = (projects[tempIntFrom].scaffolding![projects[tempIntFrom].scaffolding!.firstIndex(where: { $0.type == scaffolding.type}) ?? 0].quantity.expected) + + if $0.isEmpty || Int($0) ?? -1 > tem { + empty = true + } else { + empty = false + } + } + .textFieldStyle(TextFieldEmpty(empty: $empty)) + .keyboardType(.numberPad) + .focused($focusedField, equals: .input) + /// Code taken from: + /// https://stackoverflow.com/questions/58733003/how-to-create-textfield-that-only-accepts-numbers + /// to make sure input is of type Int and update picker + .onReceive(Just(anumber)) { newValue in + let filtered = newValue.filter { "0123456789".contains($0) } + if filtered != newValue { + self.anumber = filtered + } + } + .toolbar { + ToolbarItem(placement: .keyboard) { + Button("Done") { + focusedField = nil + } + } + } + } + .padding() + } + } + .overlay(alignment: .bottom) { + Spacer() + /// Button to start transaction for transfering scaffolding + Button(action: { + Task { + if !empty { + /// Starts transfere process if alert is confirmed + transfereMessage = "Overfør \(Int(anumber)!)x \(scaffolding.type) fra \(pickerSelectionFrom) til \(pickerSelectionTo)?" + transfereConfirmation = true + } + } + }) { + Text("Bruk") + .frame(width: 300, height: 50, alignment: .center) + } + .foregroundColor(.white) + .background(Color.blue) + .cornerRadius(10) + .padding(.bottom, 50) + } + .ignoresSafeArea(.keyboard) + .navigationTitle(Text("Overfør \(scaffolding.type)")) + .alert(isPresented: $showingConfirmation) { + /// Transfere was successful alert + Alert( + title: Text(confirmationTitle), + message: Text(confirmationMessage), + dismissButton: .default(Text("OK")) { + isShowingSheet = false + showingConfirmation = false + } + ) + } + } + .alert(isPresented: $transfereConfirmation) { + /// Checks if the user wants to proceed. If yes, execute request to the API + Alert( + title: Text("Er du sikker på at du vil fortsette?"), + message: Text(transfereMessage), + primaryButton: .default(Text("Ja")) { + transfereConfirmation = false + Task { + if !empty { + /// Starts request to transfere scaffoling + await transfereScaffoldingUnit(pickedFromID: Int(exactly: pickedFromID)!, pickedToID: Int(exactly: pickedToID)!, transfereAmount: Int(anumber)!) + } + } + // showingConfirmation = true + }, + secondaryButton: .cancel() { + transfereConfirmation = false + } + ) + } + } + + /// Establishes connection to the API and sendts a PUT request to the API with the scaffolding amount from which project to which project. + /// The code is partially taken and inspired from: + /// https://www.appsdeveloperblog.com/http-post-request-example-in-swift/ + /// - Parameters: + /// - pickedFromID: The ID of the project to transfere scaffolding from + /// - pickedToID: The ID of the project to transfere scaffolding to + /// - transfereAmount: The amount of scaffolding to transfere + func transfereScaffoldingUnit(pickedFromID: Int, pickedToID: Int, transfereAmount: Int) async { + let url = URL(string: "http://10.212.138.205:8080/stillastracking/v1/api/project/scaffolding") + guard let requestUrl = url else { fatalError() } + // Prepare URL Request Object + var request = URLRequest(url: requestUrl) + request.httpMethod = "PUT" + + /// Sets the body to be of type Scaff + let body : Scaff = Scaff(scaffold: [Move(type: "\(scaffolding.type.capitalizingFirstLetter())", quantity: transfereAmount)], toProjectID: pickedToID, fromProjectID: pickedFromID) + + /// Tries to encode body + guard let jsonData = try? JSONEncoder().encode(body) else { + print("Failed to encode order") + return + } + + /// Makes a jsonString of the encoded data + let jsonString = String(data: jsonData, encoding: .utf8)! + + /// Creates the request body with the jsonStrings data + request.httpBody = jsonString.data(using: String.Encoding.utf8); + + /// Perform HTTP Request + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + + /// Check for Error + if let error = error { + print("Error took place \(error)") + return + } + + /// Convert HTTP Response Data to a String + if let data = data, let dataString = String(data: data, encoding: .utf8) { + print("Response data string:\n \(dataString)") + } + + /// Checks the response's statuscode and displays the alert based on the code + if let response = response as? HTTPURLResponse { + if response.statusCode == 200 { + confirmationTitle = "Suksess!" + confirmationMessage = "Overføringen av \(body.scaffold[0].quantity)x \(body.scaffold[0].type) fra prosjekt \(body.fromProjectID) til prosjekt \(body.toProjectID) var en suksess!" + } else { + confirmationTitle = "Mislykket!" + confirmationMessage = "Overføringen av \(body.scaffold[0].quantity)x \(body.scaffold[0].type) fra prosjekt \(body.fromProjectID) til prosjekt \(body.toProjectID) feilet!" + } + showingConfirmation = true + } + } + task.resume() + } +} + +/* +struct TransfereScaffoldingView_Previews: PreviewProvider { + static var previews: some View { + TransfereScaffoldingView() + } +}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectListView.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectListView.swift new file mode 100644 index 0000000000000000000000000000000000000000..79aa8f0eb0972de501973687cb9944aa554c69fb --- /dev/null +++ b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/ProjectView/ProjectListView.swift @@ -0,0 +1,240 @@ +// +// ProjectView.swift +// stillasMobileApplication +// +// Created by Tormod Mork Muller on 28/03/2022. +// + +import SwiftUI +import UIKit + +/// Enum for the different filtertypes +enum FilterType { + case none, + period, + startBeforePeriod, + startAfterPeriod, + endBeforePeriod, + endAfterPeriod, + sizeBetween, + sizeEqualTo, + sizeLessThan, + sizeGreaterThan, + state, + county +} + +/// **ProjectRow** +/// The project lists information preview +struct ProjectRow: View { + /// The relevant project + var project: Project + + var body: some View { + HStack { + VStack(alignment: .leading) { + Text(project.projectName).font(.headline).bold().italic() + Text(project.period.startDate + " - " + project.period.endDate).font(.subheadline).foregroundColor(.gray) + } + Spacer() + Text(String(format: "%d", project.projectID)) + .foregroundColor(.gray) + } + } +} + +/// **ProjectListView** +/// The project list +struct ProjectListView: View { + /// Initializes an instance of ProjectData to call the loadData method + @ObservedObject var projectData: ProjectData = ProjectData() + + /// Data of searchQuery + @State var searchQuery = "" + + @State var hasFetchedData = false + + /// Initializes new Project instance to get project details + @State var projects = [Project]() + + /// Filter- or add project Modal Views active + @State private var showFilterModalView: Bool = false + @State private var showAddProjectModalView: Bool = false + + /// Initializes sort on size filter page + @State var sizeSortType: String = "Mellom" + + /// Initializes filter to be no filter + @State var filter: FilterType = .none + @State var filterArr: [String] = [] + @State var filterArrArea: [String] = [] + + // TODO: REMOVE? + /// Initialized values passed into filter + @State var projectStartDate = Date.distantPast + @State var projectEndDate = Date.distantFuture + @State var projectSize = 99999 + @State var minProjectSize = 100 + @State var maxProjectSize = 1000 + @State var projectState = "Active" + @State var projectCounty = "Innlandet" + + /// Data is loading + @State private var isLoading = true + + var body: some View { + VStack { + NavigationView { + ZStack { + /// Data is loading --> show ProgressView + if projectData.isLoading { + Spacer().frame(height:100) + ProgressView("Laster inn...") + .progressViewStyle(CircularProgressViewStyle(tint: .gray)) + .scaleEffect(x: 1.2, y: 1.2, anchor: .center) + } else { + /// Displays all projects + Form { + Section(header: Text("Alle Prosjekter")) { + /// List with all projects, filtered projects or searched for projects + List(searchResults, id: \.projectID) { project in + NavigationLink(destination: ProjectInfoView(projects: projects, project: project), label: { + ProjectRow(project: project) } + ) + } + .navigationTitle("Prosjekter") + } + } + .listStyle(.grouped) + .toolbar { + /// Button for adding filter + ToolbarItemGroup(placement: .navigationBarLeading) { + Button(action: { + print("Filter tapped!") + self.showFilterModalView.toggle() + }) { + Label("Filter", systemImage: "line.3.horizontal.decrease.circle") + } + } + + /// Button for adding project + ToolbarItemGroup(placement: .navigationBarTrailing) { + Button(action: { + print("Add project tapped!") + self.showAddProjectModalView.toggle() + }) { + Label("Legg til", systemImage: "plus.circle") + } + } + } + } + } + } + .task { + /// On first time opening --> Load in project data + await projectData.loadData { (projects) in + self.projects = projects + } + } + .sheet(isPresented: $showFilterModalView, + onDismiss: didDismiss) { + /// Modal View for filtering projects + FilterView(selStartDateBind: $projectStartDate, selEndDateBind: $projectEndDate, projectArea: $projectCounty, projectSize: $projectSize, projectStatus: $projectState, minProjectSize: $minProjectSize, maxProjectSize: $maxProjectSize, sizeSortType: $sizeSortType, filterArr: $filterArr, filterArrArea: $filterArrArea) + .onChange(of: filterArr) { filterVal in + if filterArr.contains("period") { + filter = .period + } + if filterArr.contains("area") { + filter = .county + } + if filterArr.contains("size") && sizeSortType == "Mellom" { + filter = .sizeBetween + } else if filterArr.contains("size") && sizeSortType == "Mindre enn" { + filter = .sizeLessThan + } else if filterArr.contains("size") && sizeSortType == "Større enn" { + filter = .sizeGreaterThan + } + if filterArr.contains("status") { + filter = .state + } + if filterArr.isEmpty { + filter = .none + } + } + } + .navigationViewStyle(.stack) + .searchable(text: $searchQuery, placement: .navigationBarDrawer(displayMode: .always)) + .refreshable { + /// Projectdata is fetches again from the API + await projectData.loadData { (projects) in + self.projects = projects + } + print("Refreshed") + } + } + .sheet(isPresented: $showAddProjectModalView, onDismiss: didDismiss){ + /// Modal View for adding project + /// Not implemented yet + Text("501") + .font(.system(size: 40).bold()) + Text("Not Implemented") + .font(.system(size: 30).bold()) + } + } + + /// Returns all the projects sorted and with the filter if applied + var searchResults: [Project] { + if searchQuery.isEmpty { + return filteredProjects.sorted { $0.projectName < $1.projectName } + } else { + return filteredProjects.filter { $0.projectName.lowercased().contains(searchQuery.lowercased()) }.sorted { $0.projectName < $1.projectName } + } + } + + + /// Modal View dismissed + func didDismiss() { + // Handle the dismissing action. + } + + /// The projects filtered based on the passed in filter + var filteredProjects: [Project] { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd/MM/yyyy" + + switch filter { + case .none: + return projects + case .period: + return projects.filter { dateFormatter.date(from: $0.period.startDate)! >= projectStartDate && dateFormatter.date(from: $0.period.endDate)! <= projectEndDate } + case .startBeforePeriod: + return projects.filter { dateFormatter.date(from: $0.period.startDate)! <= projectStartDate } + case .startAfterPeriod: + return projects.filter { dateFormatter.date(from: $0.period.startDate)! >= projectStartDate } + case .endBeforePeriod: + return projects.filter { dateFormatter.date(from: $0.period.endDate)! <= projectEndDate } + case .endAfterPeriod: + return projects.filter { dateFormatter.date(from: $0.period.endDate)! >= projectEndDate } + case .sizeBetween: + return projects.filter { $0.size >= Int(minProjectSize) && $0.size <= Int(maxProjectSize)} + case .sizeEqualTo: + return projects.filter { $0.size == Int(projectSize) } + case .sizeLessThan: + return projects.filter { $0.size < Int(minProjectSize) } + case .sizeGreaterThan: + return projects.filter { $0.size > Int(maxProjectSize) } + case .state: + return projects.filter { $0.state == projectState } + case .county: + return projects.filter { filterArrArea.contains($0.address.county)} + } + } +} + +/* +struct Project_Previews: PreviewProvider { + static var previews: some View { + ProjectListView() + } +} +*/ diff --git a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/TransfereScaffolding.swift b/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/TransfereScaffolding.swift deleted file mode 100644 index 360e1750bd3dde6802f857ecbc715c77f8152abb..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/ProjectView/TransfereScaffolding.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// TransfereScaffolding.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 05/04/2022. -// - -import SwiftUI - -struct TransfereScaffolding: View { - @State private var isShowingSheet = false - - var body: some View { - VStack { - Text("Transfere scaffolding") - .font(.title) - .padding(50) - Text(""" - Add transfere scaffolding functionality here. - """) - .padding(50) - Button("Dismiss", - action: { isShowingSheet.toggle() }) - } - } - - func didDismiss() { - // Handle the dismissing action. - } -} - -struct TransfereScaffolding_Previews: PreviewProvider { - static var previews: some View { - TransfereScaffolding() - } -} diff --git a/stillasMobileApplication/stillasMobileApplication/Views/Scaffolding/ScaffoldingList.swift b/stillasMobileApplication/stillasMobileApplication/Views/Scaffolding/ScaffoldingList.swift deleted file mode 100644 index 96f04645f86632aa7479b8e12fb7bb8adf950c38..0000000000000000000000000000000000000000 --- a/stillasMobileApplication/stillasMobileApplication/Views/Scaffolding/ScaffoldingList.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// ScaffoldingList.swift -// stillasMobileApplication -// -// Created by Tormod Mork Muller on 28/03/2022. -// - -import SwiftUI -/* -struct ScaffoldingList: View { - - let scaffoldingUnits = [ - ScaffoldingUnit(name: "Spir", size: "2m", amount: 1400), - ScaffoldingUnit(name: "Spir", size: "3m", amount: 1500), - ScaffoldingUnit(name: "Lengdebjelke", size: "3m", amount: 480) - ] - @State var searchQuery = "" - - var body: some View { - VStack { - NavigationView { - List (scaffoldingUnits) { scaffolding in - NavigationLink(destination: DetailView(scaffolding: scaffolding)) { ScaffoldingUnitRow (scaffolding: scaffolding) } - } - .listStyle(PlainListStyle()) - .searchable(text: $searchQuery) - .navigationTitle("Scaffolding units") - } - } - } -} - -struct ScaffoldingList_Previews: PreviewProvider { - static var previews: some View { - ScaffoldingList() - } -}*/ diff --git a/stillasMobileApplication/stillasMobileApplication/stillasMobileApplicationApp.swift b/stillasMobileApplication/stillasMobileApplication/stillasMobileApplicationApp.swift index 53c7971a765aa7da7f51ae23424ed6e3048e83ee..93bc73635cab038fe37be8daae2a9c3e23e150da 100644 --- a/stillasMobileApplication/stillasMobileApplication/stillasMobileApplicationApp.swift +++ b/stillasMobileApplication/stillasMobileApplication/stillasMobileApplicationApp.swift @@ -6,12 +6,30 @@ // import SwiftUI +import Firebase + + +/// **AppDelegate** +/// Initializes the application with FirebaseApp +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + FirebaseApp.configure() + return true + } +} @main -struct stillasMobileApplicationApp: App { +/// **stillasMobileApplication** +/// Create an app by declaring a structure that conforms to the App protocol. +/// Assigns the AppDelegate. +struct stillasMobileApplication: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + var body: some Scene { WindowGroup { + let viewModel = AppViewModel() ContentView() + .environmentObject(viewModel) } } } diff --git a/webstillas/.gitignore b/webstillas/.gitignore index 4d29575de80483b005c29bfcac5061cd2f45313e..dac8e708f644dfc5efa6830f8f6bc6ff417f96cb 100644 --- a/webstillas/.gitignore +++ b/webstillas/.gitignore @@ -21,3 +21,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + + +/src/Config/firebaseConfig.js diff --git a/webstillas/.idea/webstillas.iml b/webstillas/.idea/webstillas.iml index ef98539bbef05aef314c9713772daa8f1a8eebd0..0e106ab2f10f9ee67004dd5fbd8292f84a42045d 100644 --- a/webstillas/.idea/webstillas.iml +++ b/webstillas/.idea/webstillas.iml @@ -5,6 +5,7 @@ <excludeFolder url="file://$MODULE_DIR$/temp" /> <excludeFolder url="file://$MODULE_DIR$/.tmp" /> <excludeFolder url="file://$MODULE_DIR$/tmp" /> + <excludeFolder url="file://$MODULE_DIR$/.idea/inspectionProfiles" /> </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> diff --git a/webstillas/src/App.js b/webstillas/src/App.js deleted file mode 100644 index 2086fe1746a1a218d08f6ec3cd14ca191d5be00c..0000000000000000000000000000000000000000 --- a/webstillas/src/App.js +++ /dev/null @@ -1,55 +0,0 @@ -import './App.css'; -import React from "react"; -import { Routes, Route} from "react-router-dom"; -import {Project} from "./components/projects/projects"; -import {MapPage} from "./components/mapPage/mapPage"; -import {Scaffolding} from "./components/scaffolding/scaffolding"; -import TopBar from "./components/topBar/topBar"; -import {PreView} from "./components/projects/elements/preView"; -import Logistic from "./components/logistics/logistic"; -import { QueryClientProvider, QueryClient } from 'react-query' -import { ReactQueryDevtools } from 'react-query/devtools' -import ProtectedRoute from "./components/ProtectedRoute"; -import Login from "./components/Login"; -import Signup from "./components/Signup"; -import {UserAuthContextProvider, useUserAuth} from "./context/UserAuthContext"; -import auth from "./firebase"; -import AddProjectFunc from "./components/logistics/project/addProject"; -import AddScaffolding from "./components/logistics/scaffold/addScaffolding"; -import {UserInfo} from "./components/userinformation/userInfo"; - - - -const queryClient = new QueryClient() - -function App() { - return ( - //Authorisation of user - <UserAuthContextProvider> - {//Caching provider client - } - <QueryClientProvider client={queryClient}> - <TopBar/> {/*Topbar for the user to navigate throughout the webpage}*/} - <Routes> {/*Router that creates the routes the user is able to navigate*/} - <Route path="/prosjekt/*" element={<ProtectedRoute> <Project/></ProtectedRoute>}/> - <Route path="/kart" element={<ProtectedRoute> <MapPage/></ProtectedRoute>}/> - <Route path="/stillas" element={<ProtectedRoute> <Scaffolding/></ProtectedRoute>}/> - <Route path="/project/:id" element={<ProtectedRoute> <PreView/></ProtectedRoute>}/> - <Route path="/logistics" element={<ProtectedRoute> <Logistic/></ProtectedRoute>}/> - <Route path="/" element={<Login/>}/> - <Route path="/signup" element={<Signup/>}/> - <Route path="/addproject/" element={<ProtectedRoute> <AddProjectFunc/></ProtectedRoute>}/> - <Route path="/addscaffolding/" element={<ProtectedRoute> <AddScaffolding/></ProtectedRoute>}/> - <Route path="/userinfo/" element={<ProtectedRoute> <UserInfo/></ProtectedRoute>}/> - - - </Routes> - <ReactQueryDevtools initialIsOpen={true} /> - </QueryClientProvider> - </UserAuthContextProvider> - - ); - -} - -export default App; diff --git a/webstillas/src/components/mapPage/mapbox-marker-icon-20px-orange.png b/webstillas/src/Assets/Images/marker.png similarity index 100% rename from webstillas/src/components/mapPage/mapbox-marker-icon-20px-orange.png rename to webstillas/src/Assets/Images/marker.png diff --git a/webstillas/src/Assets/Images/profile-png-icon-2.png b/webstillas/src/Assets/Images/profile-png-icon-2.png new file mode 100644 index 0000000000000000000000000000000000000000..1a56aa21df777e242c880ae368fed349cc789ba9 Binary files /dev/null and b/webstillas/src/Assets/Images/profile-png-icon-2.png differ diff --git a/webstillas/src/Assets/Images/scaffoldingImages/Bunnskrue.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Bunnskrue.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1c2d8d3b921cd4cd937ee9c7d64cd5e14f5b81d5 Binary files /dev/null and b/webstillas/src/Assets/Images/scaffoldingImages/Bunnskrue.jpg differ diff --git a/webstillas/src/Assets/Images/scaffoldingImages/Diagonalstang.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Diagonalstang.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ec3a7f0e1827465a8ad6c846632277b32e90baf5 Binary files /dev/null and b/webstillas/src/Assets/Images/scaffoldingImages/Diagonalstang.jpg differ diff --git "a/webstillas/src/Assets/Images/scaffoldingImages/Enr\303\270rsbjelke.jpg" "b/webstillas/src/Assets/Images/scaffoldingImages/Enr\303\270rsbjelke.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..d04f496791dbdca83754df6bb0e2277991ca1b80 Binary files /dev/null and "b/webstillas/src/Assets/Images/scaffoldingImages/Enr\303\270rsbjelke.jpg" differ diff --git a/webstillas/src/Assets/Images/scaffoldingImages/Gelender.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Gelender.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d41199e6ae56f2caee998430c4b79bc798ac823e Binary files /dev/null and b/webstillas/src/Assets/Images/scaffoldingImages/Gelender.jpg differ diff --git a/webstillas/src/Assets/Images/scaffoldingImages/Lengdebjelke.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Lengdebjelke.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0678a5a2e0fa2885f57eb6e6c6e47916e643aff1 Binary files /dev/null and b/webstillas/src/Assets/Images/scaffoldingImages/Lengdebjelke.jpg differ diff --git a/webstillas/src/Assets/Images/scaffoldingImages/Plank.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Plank.jpg new file mode 100644 index 0000000000000000000000000000000000000000..671dbcad3514964e973507422557c1615a8e02cd Binary files /dev/null and b/webstillas/src/Assets/Images/scaffoldingImages/Plank.jpg differ diff --git a/webstillas/src/Assets/Images/scaffoldingImages/Rekkverksramme.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Rekkverksramme.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e78503614d298f92503e0491fb80f1e99ea105f1 Binary files /dev/null and b/webstillas/src/Assets/Images/scaffoldingImages/Rekkverksramme.jpg differ diff --git a/webstillas/src/Assets/Images/scaffoldingImages/Spir.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Spir.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b66c8b479a6a83fe4d8c54cb7e021ee75a15820a Binary files /dev/null and b/webstillas/src/Assets/Images/scaffoldingImages/Spir.jpg differ diff --git a/webstillas/src/Assets/Images/scaffoldingImages/Stillaslem.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Stillaslem.jpg new file mode 100644 index 0000000000000000000000000000000000000000..82363e50cbbb937f771bcc06fd5a6d57d4797f8c Binary files /dev/null and b/webstillas/src/Assets/Images/scaffoldingImages/Stillaslem.jpg differ diff --git a/webstillas/src/Assets/Images/scaffoldingImages/Trapp.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Trapp.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3b32dd6df4b8fbcba84914a492451c4a7356873d Binary files /dev/null and b/webstillas/src/Assets/Images/scaffoldingImages/Trapp.jpg differ diff --git a/webstillas/src/Assets/Images/scaffoldingimg.jpg b/webstillas/src/Assets/Images/scaffoldingimg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9ecccc673c8bf0f1cad9b62553e678f35aca8d6d Binary files /dev/null and b/webstillas/src/Assets/Images/scaffoldingimg.jpg differ diff --git a/webstillas/src/App.css b/webstillas/src/Assets/Styles/App.css similarity index 78% rename from webstillas/src/App.css rename to webstillas/src/Assets/Styles/App.css index 3e76366be3b6b79f551140fcf34b6c36af3123ae..7f1a941fb2025c886fc08eb12e2e345af7f99375 100644 --- a/webstillas/src/App.css +++ b/webstillas/src/Assets/Styles/App.css @@ -1,5 +1,7 @@ .link{ text-decoration: none; + color: black; + } .button{ diff --git a/webstillas/src/components/projects/elements/Modal.css b/webstillas/src/Assets/Styles/Modal.css similarity index 99% rename from webstillas/src/components/projects/elements/Modal.css rename to webstillas/src/Assets/Styles/Modal.css index 9dc167d37654f241f4ba79b73df6e262905eb303..5b694b31e8eaebcf0b3873b0980d4273b15b7687 100644 --- a/webstillas/src/components/projects/elements/Modal.css +++ b/webstillas/src/Assets/Styles/Modal.css @@ -28,7 +28,6 @@ } - .card{ width: 450px; margin-bottom: 20px; diff --git a/webstillas/src/components/scaffolding/elements/Modalscaffolding.css b/webstillas/src/Assets/Styles/Modalscaffolding.css similarity index 73% rename from webstillas/src/components/scaffolding/elements/Modalscaffolding.css rename to webstillas/src/Assets/Styles/Modalscaffolding.css index 3b75c01d060bc611200d6e800b837e273f89eef7..567445e006687fd27ab7be33c79e70bf26f3944a 100644 --- a/webstillas/src/components/scaffolding/elements/Modalscaffolding.css +++ b/webstillas/src/Assets/Styles/Modalscaffolding.css @@ -1,9 +1,10 @@ .card-scaffolding{ display: flex; + padding-top: 30px; } .list-and-btn{ - margin-top: 30px; + margin-top: 100px; margin-left: 150px; } diff --git a/webstillas/src/Assets/Styles/Spinner.css b/webstillas/src/Assets/Styles/Spinner.css new file mode 100644 index 0000000000000000000000000000000000000000..00f0984a66739f2e72b6ebaeefa6021266ca555e --- /dev/null +++ b/webstillas/src/Assets/Styles/Spinner.css @@ -0,0 +1,5 @@ + +.spinner{ + margin-top: 20%; + margin-left: 47%; +} diff --git a/webstillas/src/components/logistics/project/addProject.css b/webstillas/src/Assets/Styles/addProject.css similarity index 100% rename from webstillas/src/components/logistics/project/addProject.css rename to webstillas/src/Assets/Styles/addProject.css diff --git a/webstillas/src/components/logistics/scaffold/addScaffolding.css b/webstillas/src/Assets/Styles/addScaffolding.css similarity index 88% rename from webstillas/src/components/logistics/scaffold/addScaffolding.css rename to webstillas/src/Assets/Styles/addScaffolding.css index 0bea57a075d09bdb40abd2230762c3645a168d1a..bc9e9b8f1386e7e2bf46613ca41bfbe64db14210 100644 --- a/webstillas/src/components/logistics/scaffold/addScaffolding.css +++ b/webstillas/src/Assets/Styles/addScaffolding.css @@ -12,7 +12,7 @@ .input-fields{ display: flow; - margin-left: 35%; + margin-left: 40%; margin-top: 10%; width: 500px; align-content: center; @@ -28,3 +28,7 @@ margin-left: 35%; margin-top: 7%; } + +.alert-success{ + width: 440px; +} diff --git a/webstillas/src/components/projects/elements/card.css b/webstillas/src/Assets/Styles/card.css similarity index 92% rename from webstillas/src/components/projects/elements/card.css rename to webstillas/src/Assets/Styles/card.css index a6542bf37768d90a295a247cfe689d2a9ad78bc1..4e56ad67d98d31f7d8c0df241bcd9dfccea710ce 100644 --- a/webstillas/src/components/projects/elements/card.css +++ b/webstillas/src/Assets/Styles/card.css @@ -14,11 +14,6 @@ overflow: hidden; } -.btn-delete{ - height: fit-content; - width: fit-content; - background-size: cover; -} .header { display: flex; @@ -66,9 +61,6 @@ border-left-color: gray; } -.card-btn{ - padding: 0 24px 24px; -} .btn{ background-color: #F28A04; border-radius: 10px; @@ -82,9 +74,10 @@ .card-btns{ display: flex; - padding-top: 24px; align-items: center; justify-content: center; + padding: 0 24px 24px; + } .left-contact-text{ diff --git a/webstillas/src/Assets/Styles/firebaselogin.css b/webstillas/src/Assets/Styles/firebaselogin.css new file mode 100644 index 0000000000000000000000000000000000000000..3731ebe60b71162e92d0ba096947d3047c67b8fb --- /dev/null +++ b/webstillas/src/Assets/Styles/firebaselogin.css @@ -0,0 +1,56 @@ +.signup-box{ + width: 50%; + margin-left: 25%; + margin-top: 5%; + padding: 20px 20px 20px 20px; +} + +.selectors{ + display: flex; +} + +.admin-select{ + padding-right: 40px; +} + +.role-select{ + padding-right: 40px; +} + +.date-picker-signup{ + padding-top: 10px; + padding-bottom: 10px; +} + +.date{ + padding-right: 20px; +} + +.signup-btn{ + margin-left: 250px; +} + +.signup-text { + padding-top: 10px; + margin-left: 250px; + +} + +.box{ + padding: 20px 20px 20px 20px; +} + +.loginpage{ + margin-left: 35%; + margin-top: 5%; +} + +.loginbtn{ + margin-left: 20%; +} + +.logintxt{ + margin-left: 25%; + margin-top: -5%; + padding-bottom: 10px; +} diff --git a/webstillas/src/components/logistics/project/map.css b/webstillas/src/Assets/Styles/map.css similarity index 100% rename from webstillas/src/components/logistics/project/map.css rename to webstillas/src/Assets/Styles/map.css diff --git a/webstillas/src/components/mapPage/mapPage.css b/webstillas/src/Assets/Styles/mapPage.css similarity index 74% rename from webstillas/src/components/mapPage/mapPage.css rename to webstillas/src/Assets/Styles/mapPage.css index bc50c93a9fb373074ebf61d4f20eb05eceb51dfe..a0dd433ce04f09becc9c5c848496f043225c0a61 100644 --- a/webstillas/src/components/mapPage/mapPage.css +++ b/webstillas/src/Assets/Styles/mapPage.css @@ -5,7 +5,6 @@ } .marker { - background-image: url('/src/components/mapPage/mapbox-marker-icon-20px-orange.png'); background-size: cover; width: 50px; height: 50px; diff --git a/webstillas/src/components/projects/elements/preView.css b/webstillas/src/Assets/Styles/preView.css similarity index 94% rename from webstillas/src/components/projects/elements/preView.css rename to webstillas/src/Assets/Styles/preView.css index c987c4ca49fb222f434926d841e701aa51b4970b..0e6a758e1944f5427e1c05f2f7fef39ce643c2d5 100644 --- a/webstillas/src/components/projects/elements/preView.css +++ b/webstillas/src/Assets/Styles/preView.css @@ -56,3 +56,6 @@ width: 600px; } +.contact-information{ + margin-top: 50px; +} diff --git a/webstillas/src/components/projects/projects.css b/webstillas/src/Assets/Styles/projects.css similarity index 98% rename from webstillas/src/components/projects/projects.css rename to webstillas/src/Assets/Styles/projects.css index 0447e690ffb8c835d034dffbd83bc4c9907a5261..e4aa57269522176d72a892e3a6a6a50d0209a363 100644 --- a/webstillas/src/components/projects/projects.css +++ b/webstillas/src/Assets/Styles/projects.css @@ -28,10 +28,13 @@ input[type=number]::-webkit-outer-spin-button { } +body{ + background-color: #9f9e9e; +} + .main-project-window { display: flex; - background-color: #f9f9f9; } diff --git a/webstillas/src/components/scaffolding/scaffolding.css b/webstillas/src/Assets/Styles/scaffolding.css similarity index 95% rename from webstillas/src/components/scaffolding/scaffolding.css rename to webstillas/src/Assets/Styles/scaffolding.css index 4519052cb16603d34b40b7a6c390de9a69a4142c..aee32c0588e3f6daec115e20c90e92b6da4da7a5 100644 --- a/webstillas/src/components/scaffolding/scaffolding.css +++ b/webstillas/src/Assets/Styles/scaffolding.css @@ -9,7 +9,6 @@ .input-sorting-text{ margin-bottom: 0; display: flex; - justify-content: center; font-weight: initial; font-size: 15px; } diff --git a/webstillas/src/components/scaffolding/elements/scaffoldingCard.css b/webstillas/src/Assets/Styles/scaffoldingCard.css similarity index 100% rename from webstillas/src/components/scaffolding/elements/scaffoldingCard.css rename to webstillas/src/Assets/Styles/scaffoldingCard.css diff --git a/webstillas/src/Assets/Styles/topBar.css b/webstillas/src/Assets/Styles/topBar.css new file mode 100644 index 0000000000000000000000000000000000000000..a04ea4eb1cca1f4d09d936adbacb81b18b56bede --- /dev/null +++ b/webstillas/src/Assets/Styles/topBar.css @@ -0,0 +1,43 @@ +.toolbar{ + background: #F28A04; + justify-content: space-between; +} + +.dropdown-toggle-topbar{ + height: 70px; + width: 150px; + border-style: none; + border-radius: 0; + +} + + +.dropdown-toggle-topbar:hover .button{ + background-color:#d07506; ; +} + +.button{ + width: 150px; + height: 70px; +} + +.links{ + margin-inside: 50px; +} + +.appbar{ + position: fixed; +} + + +.link-dropdown{ + text-decoration-color: black; +} + +.user-button-topbar{ + justify-content: center; +} + +.profile-img{ + +} diff --git a/webstillas/src/components/userinformation/userInfo.css b/webstillas/src/Assets/Styles/userInfo.css similarity index 99% rename from webstillas/src/components/userinformation/userInfo.css rename to webstillas/src/Assets/Styles/userInfo.css index db9d96897ecc2b5206a7ed9abe22be7a825f940a..1c314746eae6f89bb7015b211b80b687096fceea 100644 --- a/webstillas/src/components/userinformation/userInfo.css +++ b/webstillas/src/Assets/Styles/userInfo.css @@ -29,6 +29,9 @@ body { margin-left: 35%; } + + + .info-card{ alignment: center; width: fit-content; diff --git a/webstillas/src/context/UserAuthContext.js b/webstillas/src/Config/UserAuthContext.js similarity index 60% rename from webstillas/src/context/UserAuthContext.js rename to webstillas/src/Config/UserAuthContext.js index bf428001e92a30c4c502c0cbff96a072ff6b688c..f5dc8fe203ce763167327a21a456f8c509b2247a 100644 --- a/webstillas/src/context/UserAuthContext.js +++ b/webstillas/src/Config/UserAuthContext.js @@ -5,23 +5,53 @@ import { onAuthStateChanged, signOut, } from "firebase/auth"; -import { auth } from "../firebase"; +import { auth } from "./firebase"; const userAuthContext = createContext(); //Hentet fra https://github.com/WebDevSimplified/React-Firebase-Auth + +/** + *Function that handles firebase log in and sign up. + * + * @param children + * @returns {JSX.Element} + */ export function UserAuthContextProvider({ children }) { const [user, setUser] = useState({}); + /** + * Function to handle login from firebase. + * + * @param email the registered email of the user + * @param password the corresponding password that matches the users email. + * @returns {Promise<UserCredential>} + */ function logIn(email, password) { return signInWithEmailAndPassword(auth, email, password); } + + /** + * Function to sign up a new user to firebase. + * + * @param email of the user that is to be registered. + * @param password of the user that is to be registered. + * @returns {Promise<UserCredential>} + */ function signUp(email, password) { return createUserWithEmailAndPassword(auth, email, password); } + + /** + * Function that will log a user out of the system. + * @returns {Promise<void>} + */ function logOut() { return signOut(auth); } + + + //todo kommenter useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (currentuser) => { console.log("Auth", currentuser); @@ -32,6 +62,8 @@ export function UserAuthContextProvider({ children }) { }; }, []); + + return ( <userAuthContext.Provider value={{ user, logIn, signUp, logOut }} diff --git a/webstillas/src/Config/firebase.js b/webstillas/src/Config/firebase.js new file mode 100644 index 0000000000000000000000000000000000000000..98165b259b27fdaa277e263bb92d62e4282e05e8 --- /dev/null +++ b/webstillas/src/Config/firebase.js @@ -0,0 +1,24 @@ +import { initializeApp } from "firebase/app"; +import { getAuth } from "firebase/auth"; +import { + FirbaseAPIKEY, FirbaseAppID, + FirbaseMessagingSenderId, + FirbaseprojectId, + FirbaseStorageBucket, + FirebaseAuthDomain +} from "./firebaseConfig"; + +//Firebase configuration to database. +const firebaseConfig = { + apiKey: FirbaseAPIKEY, + authDomain: FirebaseAuthDomain, + projectId: FirbaseprojectId, + storageBucket: FirbaseStorageBucket, + messagingSenderId: FirbaseMessagingSenderId, + appId: FirbaseAppID +}; + +// Initialize Firebase. +const app = initializeApp(firebaseConfig); +export const auth = getAuth(app); +export default app; diff --git a/webstillas/src/modelData/constantsFile.js b/webstillas/src/Constants/apiURL.js similarity index 87% rename from webstillas/src/modelData/constantsFile.js rename to webstillas/src/Constants/apiURL.js index 944117bb987e530e25fa1cb938f14dbed1b95c85..14d1732e4c275b698324c834c0d4f34128bf4781 100644 --- a/webstillas/src/modelData/constantsFile.js +++ b/webstillas/src/Constants/apiURL.js @@ -3,9 +3,8 @@ export const BASE_URL = "http://10.212.138.205:8080/stillastracking/v1/api/" export const SCAFFOLDING_URL = "unit" export const STORAGE_URL = "storage" export const USER_URL = "user?id=" +export const USER_POST_URL = "user" export const PROJECTS_URL = "project" -export const PROJECTS_URL_WITH_SCAFFOLDING = "project?scaffolding=true" - export const PROJECTS_URL_WITH_ID = "project?id=" export const WITH_SCAFFOLDING_URL = "&scaffolding=true" export const MAP_STYLE_V11 = "mapbox://styles/mapbox/streets-v11" diff --git a/webstillas/src/Constants/webURL.js b/webstillas/src/Constants/webURL.js new file mode 100644 index 0000000000000000000000000000000000000000..c3aed2aecc5480cc8e7e93b09b5a42ea3e669a84 --- /dev/null +++ b/webstillas/src/Constants/webURL.js @@ -0,0 +1,16 @@ +export const PROJECT_URL = "/project" +export const MAP_URL = "/map" +export const SCAFFOLDING_URL = "/scaffolding" +export const PROJECT_URL_ID = "/project/:id" +export const ADD_PROJECT_URL = "/add-project" +export const ADD_SCAFFOLDING_URL = "/add-scaffolding" +export const USERINFO_URL = "/userinfo" +export const NOTFOUND = "/*" +export const SIGNUP = "/signup" +export const LOGIN = "/" + + + + + + diff --git a/webstillas/src/components/projects/tabView/Tab.js b/webstillas/src/Layout/tabView/Tab.js similarity index 86% rename from webstillas/src/components/projects/tabView/Tab.js rename to webstillas/src/Layout/tabView/Tab.js index f2ee28e12084c2e8c2285f6200a69bc895d77df7..7247f90533d2168412257dd9539407fe678f6554 100644 --- a/webstillas/src/components/projects/tabView/Tab.js +++ b/webstillas/src/Layout/tabView/Tab.js @@ -2,7 +2,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -//https://www.digitalocean.com/community/tutorials/react-tabs-component +/** + * Class of a single tab. + * + * Code acquired from https://www.digitalocean.com/community/tutorials/react-tabs-component + */ class Tab extends React.Component { static propTypes = { activeTab: PropTypes.string.isRequired, diff --git a/webstillas/src/components/projects/tabView/Tabs.js b/webstillas/src/Layout/tabView/Tabs.js similarity index 92% rename from webstillas/src/components/projects/tabView/Tabs.js rename to webstillas/src/Layout/tabView/Tabs.js index 7a7b65f35b2d3fcbf1bf9937ed8276b189c0f41e..027fb36febfbb9a6c10bc57db42fbad8bbef788b 100644 --- a/webstillas/src/components/projects/tabView/Tabs.js +++ b/webstillas/src/Layout/tabView/Tabs.js @@ -2,7 +2,12 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Tab from './Tab'; -//https://www.digitalocean.com/community/tutorials/react-tabs-component + +/** + * Class to handle tabs + * + * Code aquired from https://www.digitalocean.com/community/tutorials/react-tabs-component + */ class Tabs extends Component { static propTypes = { children: PropTypes.instanceOf(Array).isRequired, diff --git a/webstillas/src/Layout/topBar/topBar.js b/webstillas/src/Layout/topBar/topBar.js new file mode 100644 index 0000000000000000000000000000000000000000..3213af42806e1ba2ef4dad6cb0ed900b4bee8cf0 --- /dev/null +++ b/webstillas/src/Layout/topBar/topBar.js @@ -0,0 +1,105 @@ +import React from "react"; +import '../../Assets/Styles/topBar.css'; +import { + AppBar, Toolbar, Button +} from '@material-ui/core'; +import {Link} from "react-router-dom"; +import {Dropdown } from "react-bootstrap"; +import DropdownItem from "react-bootstrap/DropdownItem"; +import {useUserAuth} from "../../Config/UserAuthContext"; +import {auth} from "../../Config/firebase" +import {GetCachingData} from "../../Middleware/addData"; +import {USER_URL} from "../../Constants/apiURL"; +import {SpinnerDefault} from "../../components/Indicators/Spinner"; +import "bootstrap/dist/css/bootstrap.min.css"; +import {ADD_PROJECT_URL, ADD_SCAFFOLDING_URL, MAP_URL, PROJECT_URL, SCAFFOLDING_URL, USERINFO_URL} from "../../Constants/webURL"; +import DropdownToggle from "react-bootstrap/DropdownToggle"; +import DropdownMenu from "react-bootstrap/DropdownMenu"; +import profileImg from "../../Assets/Images/profile-png-icon-2.png" + +/** + Component that will be used as a top bar for the user to navigate throughout the application. + */ + +const TopBar = () => { + const {logOut} = useUserAuth(); + + let loading, user + + + //If the user is authenticated, fetch data from database + if (auth.currentUser){ + const {isLoading, data} = GetCachingData("user", USER_URL + auth.currentUser.uid) + loading = isLoading + user = data + } + + /* + If the user is not authenticated, the topbar will be empty. + */ + if (!auth.currentUser) { + return ( + <AppBar position="sticky"> + <Toolbar className="toolbar"> + </Toolbar> + </AppBar> + ) + } else if (loading) { + //If data is loading, the user will get a spinner displayed + return <SpinnerDefault/> + } else { + const userData = JSON.parse(user.text) + //Top bar with interactive buttons to navigate. + return ( + <AppBar position="sticky"> + <Toolbar className="toolbar"> + <Link className="link" to={PROJECT_URL}> + <Button className="button">Prosjekter</Button> + </Link> + <Link className="link" to={SCAFFOLDING_URL}> + <Button className="button">Stillasdeler</Button> + </Link> + <Link className="link" to={MAP_URL}> + <Button className="button">Kart</Button> + </Link> + + <Dropdown> + <DropdownToggle className={"dropdown-toggle-topbar"} variant=" primary" id="dropdown-basic"> + Logistikk + </DropdownToggle> + <DropdownMenu> + <DropdownItem className={"dropdown-item-topbar"}> + <Link className={"dropdown-item"} + to={ADD_PROJECT_URL}>Legg til prosjekt </Link> + </DropdownItem> + <DropdownItem> + <Link className={"dropdown-item"} + to={ADD_SCAFFOLDING_URL}>Legg til stillas</Link> + </DropdownItem> + </DropdownMenu> + </Dropdown> + + <Dropdown> + <DropdownToggle className={"dropdown-toggle-topbar user-button-topbar"} variant=" primary" id="dropdown-basic"> + <img src={profileImg} alt={""} style={{height: "30px"}}/> + <p>{userData?.name.firstName}</p> + </DropdownToggle> + <DropdownMenu > + <DropdownItem > + <Link className={"dropdown-item"} + to={USERINFO_URL}> + Bruker Informasjon</Link> + </DropdownItem> + <div className="dropdown-divider"></div> + <DropdownItem + className={"dropdown-item"} + onClick={logOut}>Logg ut</DropdownItem> + </DropdownMenu> + </Dropdown> + </Toolbar> + </AppBar> + ); + } +} + +export default TopBar; diff --git a/webstillas/src/Middleware/addData.js b/webstillas/src/Middleware/addData.js new file mode 100644 index 0000000000000000000000000000000000000000..1025810cd22ce1e8d5d30603267378be8b53a3a7 --- /dev/null +++ b/webstillas/src/Middleware/addData.js @@ -0,0 +1,27 @@ +import React from 'react' +import fetchModel from "./fetchData"; +import { useQuery } from 'react-query' + +//Todo set timeout + +/** + * Function that will fetch data from api, and cache data + * + * @param dataName key to data caching + * @param url to the api + * @returns {{isLoading: boolean, isLoadingError: boolean, isError: boolean, data: unknown}} + */ +export const GetCachingData = (dataName, url) => { + const { isLoading, data, isError, isLoadingError} = useQuery(dataName, ()=>{ + return fetchModel(url) + }, { + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false + + }) + return {isLoading, data, isError, isLoadingError} +} + + + diff --git a/webstillas/src/modelData/deleteProject.js b/webstillas/src/Middleware/deleteProject.js similarity index 67% rename from webstillas/src/modelData/deleteProject.js rename to webstillas/src/Middleware/deleteProject.js index bfb7f834361201f0ea9b1742d3b16a85b96f4eeb..20fc25778a85045366e1fd61325a5370672e02e0 100644 --- a/webstillas/src/modelData/deleteProject.js +++ b/webstillas/src/Middleware/deleteProject.js @@ -1,5 +1,12 @@ -import {BASE_URL} from "./constantsFile" +import {BASE_URL} from "../Constants/apiURL" +/** + * Function that will send a delete request to an api. + * + * @param url to the request we would like to send + * @param body request body + * @returns {Promise<unknown>} + */ export default function deleteModel(url, body) { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); @@ -18,7 +25,11 @@ export default function deleteModel(url, body) { text: xhr.responseText }))); } else { - resolve((xhr.responseText)); + resolve({ + statusCode: xhr.status, + text: xhr.responseText + } + ); } }); xhr.open('DELETE', BASE_URL + url); diff --git a/webstillas/src/modelData/fetchData.js b/webstillas/src/Middleware/fetchData.js similarity index 59% rename from webstillas/src/modelData/fetchData.js rename to webstillas/src/Middleware/fetchData.js index c18a1dfdb4cec8898f701b2e70497bdd97811c1d..d370a31dcf4272d2cdda217aae20ea58bdc4fd6d 100644 --- a/webstillas/src/modelData/fetchData.js +++ b/webstillas/src/Middleware/fetchData.js @@ -1,24 +1,31 @@ -import {BASE_URL} from "./constantsFile" +import {BASE_URL} from "../Constants/apiURL" +/** + * Function to fetch data from an api + * + * @param url we would like to fetch data from. + * @returns {Promise<unknown>} + */ export default function fetchModel(url) { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); - /* - load event is also ok to use here, - but readystatechange was giving me more descriptive errors - */ + xhr.addEventListener('readystatechange', () => { if (xhr.readyState !== 4) { return; } if (xhr.status !== 200) { - reject(new Error(JSON.stringify({ + reject({ status: xhr.status, statusText: xhr.statusText, text: xhr.responseText - }))); + }); } else { - resolve(JSON.parse(xhr.responseText)); + resolve({ + statusCode: xhr.status, + text: xhr.responseText + } + ); } }); xhr.open('GET', BASE_URL + url); diff --git a/webstillas/src/modelData/postModel.js b/webstillas/src/Middleware/postModel.js similarity index 65% rename from webstillas/src/modelData/postModel.js rename to webstillas/src/Middleware/postModel.js index 48fb7b976507b8d68dd62641af4657131b94d92b..442ca96a66eae887a6ca22ad4c12d063efb6c8a2 100644 --- a/webstillas/src/modelData/postModel.js +++ b/webstillas/src/Middleware/postModel.js @@ -1,5 +1,12 @@ -import {BASE_URL} from "./constantsFile"; +import {BASE_URL} from "../Constants/apiURL"; +/** + * Function to post data to an api + * + * @param url of the request we would like to send. + * @param body post body the user is sending to the api + * @returns {Promise<unknown>} + */ export default function postModel(url, body) { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); @@ -15,12 +22,16 @@ export default function postModel(url, body) { } if (xhr.status !== 201) { reject(new Error(JSON.stringify({ - status: xhr.status, + statusCode: xhr.status, statusText: xhr.statusText, text: xhr.responseText }))); } else { - resolve((xhr.responseText)); + resolve({ + statusCode: xhr.status, + text: xhr.responseText + } + ); } }); diff --git a/webstillas/src/modelData/putData.js b/webstillas/src/Middleware/putData.js similarity index 61% rename from webstillas/src/modelData/putData.js rename to webstillas/src/Middleware/putData.js index 6825019a02366a2571555ca694f5afb704f8e703..53f87b7852e20724d8104dd3d04111c4a19e5df2 100644 --- a/webstillas/src/modelData/putData.js +++ b/webstillas/src/Middleware/putData.js @@ -1,5 +1,13 @@ -import {BASE_URL} from "./constantsFile"; +import {BASE_URL} from "../Constants/apiURL"; + +/** + * Function to change data to the API + * + * @param url of the request we would like to send. + * @param body put body the user is sending to the api + * @returns {Promise<unknown>} + */ export default function putModel(url, body) { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); @@ -14,13 +22,17 @@ export default function putModel(url, body) { return; } if (xhr.status !== 200) { - reject(new Error(JSON.stringify({ - status: xhr.status, + reject((({ + statusCode: xhr.status, statusText: xhr.statusText, text: xhr.responseText }))); } else { - resolve((xhr.responseText)); + resolve({ + statusCode: xhr.status, + text: xhr.responseText + } + ); } }); xhr.send(body); diff --git a/webstillas/src/components/Login.js b/webstillas/src/Pages/Login.js similarity index 69% rename from webstillas/src/components/Login.js rename to webstillas/src/Pages/Login.js index 6524237795d0542b832bd7ce8a56958c09ef764d..d773da8897214d9ef0f82dec296c00322fe66465 100644 --- a/webstillas/src/components/Login.js +++ b/webstillas/src/Pages/Login.js @@ -2,8 +2,16 @@ import React, { useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { Form, Alert } from "react-bootstrap"; import { Button } from "react-bootstrap"; -import { useUserAuth } from "../context/UserAuthContext"; +import { useUserAuth } from "../Config/UserAuthContext"; +import {PROJECT_URL, SIGNUP} from "../Constants/webURL"; +import "../Assets/Styles/firebaselogin.css" + +/** + * Function to display login site. + * @returns {JSX.Element} + * @constructor + */ const Login = () => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); @@ -11,12 +19,17 @@ const Login = () => { const { logIn } = useUserAuth(); const navigate = useNavigate(); + /** + * Function to submit the users request to log in. On success navigate to project site. + * @param e forms submit + * @returns {Promise<void>} + */ const handleSubmit = async (e) => { e.preventDefault(); setError(""); try { await logIn(email, password); - navigate("/prosjekt"); + navigate(PROJECT_URL); } catch (err) { setError("Feil brukernavn eller passord. \nVennligst prøv igjen"); } @@ -24,8 +37,8 @@ const Login = () => { return ( - <> - <div className="p-4 box"> + <div className={"card loginpage"}> + <div className="box"> <h2 className="mb-3">Stillas Login</h2> {error && <Alert variant="danger">{error}</Alert>} <Form onSubmit={handleSubmit}> @@ -45,18 +58,18 @@ const Login = () => { /> </Form.Group> - <div className="d-grid gap-2"> + <div className="loginbtn"> <Button variant="primary" type="Submit"> - Log In + Logg inn </Button> </div> </Form> <hr /> </div> - <div className="p-4 box mt-3 text-center"> - Don't have an account? <Link to="/signup">Sign up</Link> + <div className="logintxt"> + Har du ikke en bruker? <Link to={SIGNUP}>Registrer</Link> </div> - </> + </div> ); }; diff --git a/webstillas/src/Pages/Signup.js b/webstillas/src/Pages/Signup.js new file mode 100644 index 0000000000000000000000000000000000000000..8ac81235731fcca1a3323c37959352c22ddb0b81 --- /dev/null +++ b/webstillas/src/Pages/Signup.js @@ -0,0 +1,146 @@ +import React, {useState} from "react"; +import {Link, useNavigate} from "react-router-dom"; +import {Form, Alert} from "react-bootstrap"; +import {Button} from "react-bootstrap"; +import {useUserAuth} from "../Config/UserAuthContext"; +import postModel from "../Middleware/postModel"; +import {formatDateToString} from "./projects"; +import {LOGIN} from "../Constants/webURL"; +import {USER_POST_URL} from "../Constants/apiURL"; +import "../Assets/Styles/firebaselogin.css" + +/** + * Function that will register a new user to the system. + * + * @returns {JSX.Element} + * @constructor + */ +const Signup = () => { + const [email, setEmail] = useState(""); + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + const [role, setRole] = useState(""); + const [phone, setPhone] = useState(0); + const [admin, setAdmin] = useState(false); + const [birthDay, setBirthDay] = useState(""); + const [error, setError] = useState(""); + const [password, setPassword] = useState(""); + const {signUp} = useUserAuth(); + let navigate = useNavigate(); + + /** + * Will sign up the user, then add the user to the database. + * On sucsess the user is navigated back to log in site + * + * @returns {Promise<void>} + */ + const handleSubmit = async () => { + try { + signUp(email, password).then(newUser => { + const user = + { + "employeeID": newUser.user.uid, + "name": { + "firstName": firstName, + "lastName": lastName + }, + "role": role, + "phone": phone, + "email": email, + "admin": admin, + "dateOfBirth": birthDay + } + JSON.stringify(user) + postModel(USER_POST_URL, user) + .then(() => navigate(LOGIN)) + + }) + } catch (err) { + console.log(err) + setError(err.message); + } + }; + + + return ( + <div className={"card signup-box"}> + <h2 className="mb-3">Stillas bruker registrering </h2> + {error && <Alert variant="danger">{error}</Alert>} + <Form onSubmit={handleSubmit}> + <Form.Group className="mb-3" controlId="firstName"> + <Form.Control + type="text" + placeholder="Fornavn" + onChange={(e) => setFirstName(e.target.value)} + /> + </Form.Group> + <Form.Group className="mb-3" controlId="lastName"> + <Form.Control + type="text" + placeholder="Etternavn" + onChange={(e) => setLastName(e.target.value)} + /> + </Form.Group> + + <Form.Group className="mb-3" controlId="lastName"> + <Form.Control + type="number" + placeholder="Telefonnummer" + onChange={(e) => setPhone(Number(e.target.value))} + /> + </Form.Group> + + <div className={"selectors"}> + <Form.Group className="admin-select" controlId="admin"> + <Form.Select onChange={(e) => setAdmin(Boolean(e.target.value))}> + <option value={"false"}>False</option> + <option value={"true"}>True</option> + </Form.Select> + </Form.Group> + + <Form.Group className="role-select" controlId="role"> + <Form.Select onChange={(e) => setRole(e.target.value)}> + <option value={"admin"}>Administrator</option> + <option value={"installer"}>Installatør</option> + <option value={"storage"}>Lagerarbeider</option> + + </Form.Select> + </Form.Group> + </div> + + + <div className={"date-picker-signup"}> + <label className={"date"} htmlFor="startDate">Fødselsdag</label> + <input type="date" onChange={(event) => setBirthDay(formatDateToString(event.target.value))}/> + </div> + + <Form.Group className="mb-3" controlId="formBasicEmail"> + <Form.Control + type="email" + placeholder="Email" + onChange={(e) => setEmail(e.target.value)} + /> + </Form.Group> + <Form.Group className="mb-3" controlId="formBasicPassword"> + <Form.Control + type="password" + placeholder="Passord" + onChange={(e) => setPassword(e.target.value)} + /> + </Form.Group> + <div className="signup-btn"> + <Button variant="primary" type="Submit"> + Registrer + </Button> + + </div> + </Form> + <div className="signup-text "> + Har du allerede en bruker? <Link to={LOGIN}>Logg inn</Link> + </div> + </div> + ); +}; + + +export default Signup; diff --git a/webstillas/src/components/logistics/project/map.js b/webstillas/src/Pages/addProjectMap.js similarity index 55% rename from webstillas/src/components/logistics/project/map.js rename to webstillas/src/Pages/addProjectMap.js index 41825aeeea257dbac774864a8f410f1d1c23bad9..8eee79ff01e63fe964d8d224df0dcb715392fcee 100644 --- a/webstillas/src/components/logistics/project/map.js +++ b/webstillas/src/Pages/addProjectMap.js @@ -2,42 +2,60 @@ import React, {useState} from "react"; import ReactMapboxGl, {ZoomControl} from "react-mapbox-gl"; import DrawControl from "react-mapbox-gl-draw"; import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"; -import postModel from "../../../modelData/postModel"; -import {PROJECTS_URL} from "../../../modelData/constantsFile"; +import postModel from "../Middleware/postModel"; +import {MAP_STYLE_V11, PROJECTS_URL} from "../Constants/apiURL"; import {useQueryClient} from "react-query"; import polygon from "@mapbox/mapbox-gl-draw/src/feature_types/polygon"; -import "./map.css" - -const Map = ReactMapboxGl({ - accessToken: - "pk.eyJ1IjoiYWxla3NhYWIxIiwiYSI6ImNrbnFjbms1ODBkaWEyb3F3OTZiMWd6M2gifQ.vzOmLzHH3RXFlSsCRrxODQ" +import "../Assets/Styles/map.css" +import {MapBoxAPIKey} from "../Config/firebaseConfig"; +import {AlertCatch} from "../components/Indicators/error"; +import {Button, Spinner} from "react-bootstrap"; +import {PROJECT_URL} from "../Constants/webURL"; +import {useNavigate} from "react-router-dom"; + +const AddProjectMap = ReactMapboxGl({ + accessToken: MapBoxAPIKey }); +/** + * Function that will display a map, and allow a user to draw a polygon. + * + * @param props variables sent from previous view. + * @returns {JSX.Element} AddProjectMap with draw controllers. + */ export function MapClass(props) { + //Query client that will manage the caching. const queryClient = useQueryClient() - const [ok, setOk] = useState(false) + let navigate = useNavigate(); - console.log(props.props) + //Setting variables + const [ok, setOk] = useState(false) const [mapInfo, setMapInfo] = useState([]) + const [buttonPressed, setButtonPressed] = useState(false) + + /** + * Function that will validate the polygon the user has drawn. + * If the user has drawn a polygon more than 4 points, the user will get a warning. + * + * @param features is the return value from the polygon. + */ const onDrawCreate = ({features}) => { if (features[0].geometry.coordinates[0].length !== 5) { - console.log("length is invalid") - window.alert("Invalid geo format. Only valid is 4 points") - console.log(features) + window.alert("Format ikke godkjent! Kun fire punkter er tillatt ") } else { - console.log(features) setMapInfo(features) setOk(true) - } - }; + + //Defines the geofence and project object let geofence = null let project = (props.props) for (const mapInfoElement of mapInfo) { + //if the user has added a valid polygon for the system. the geofence object is initialized. if ((mapInfoElement).hasOwnProperty('geometry')) { geofence = { "w-position": { @@ -58,29 +76,38 @@ export function MapClass(props) { }, } } - console.log(geofence) + //The geofence is added to project object. project = {...project, geofence} } + /** + * Function that will add the project to the API. + * + * @returns {Promise<void>} + * @constructor + */ const AddProjectRequest = async () => { - try { - console.log(JSON.stringify(project)) - - await postModel(PROJECTS_URL, JSON.stringify(project)) - await queryClient.refetchQueries("allProjects") + try { + setButtonPressed(true) + await postModel(PROJECTS_URL, (project)) + await queryClient.refetchQueries("allProjects").then( + navigate(PROJECT_URL) + ) } catch (e) { - console.log(e) + setButtonPressed(true) + AlertCatch() } - } + + return ( <div className="App"> <div className={"map"}> - <Map - style="mapbox://styles/mapbox/streets-v9" // eslint-disable-line + <AddProjectMap + style={MAP_STYLE_V11} containerStyle={{ height: "80vh", width: "50vw" @@ -99,20 +126,31 @@ export function MapClass(props) { uncombine_features: false }} default_mode={"polygon"} - clickBuffer={5} onDrawDelete={() => setOk(false)} - /> <ZoomControl position="bottom-right" /> - </Map> + </AddProjectMap> </div> - <button className={"confirm-btn"} disabled={!ok || !props.valid} onClick={AddProjectRequest}>Add Project</button> - </div> - ); + + {buttonPressed ? <Button className={"confirm-btn"} + disabled> + <Spinner + as="span" + animation="grow" + size="sm" + role="status" + aria-hidden="true" + /> + Legger til + </Button> : + <button className={"confirm-btn"} disabled={!ok || !props.valid} onClick={AddProjectRequest}>Add Project</button>} + + </div> + ); } diff --git a/webstillas/src/Pages/addScaffolding.js b/webstillas/src/Pages/addScaffolding.js new file mode 100644 index 0000000000000000000000000000000000000000..89a6e945aa37d82607a26912f418516f283b319b --- /dev/null +++ b/webstillas/src/Pages/addScaffolding.js @@ -0,0 +1,117 @@ +import React, {useState} from 'react' +import postModel from "../Middleware/postModel"; +import {SCAFFOLDING_URL} from "../Constants/apiURL"; +import "../Assets/Styles/addScaffolding.css" +import {Alert} from "react-bootstrap"; + + +/** + * Function that will allow the user to add a new scaffolding unit + * @returns {JSX.Element} + */ +function AddScaffolding() { + + //Defining the json body to add a new unit + const [scaffolding, setScaffolding] = useState({ + id: "", + type: "", + batteryLevel: 100, + location: { + longitude: 0, + latitude: 0, + address: "" + } + }) + //Verification of a buttonPress + const [buttonPress, setButtonPress] = useState(false) + + + /** + * Returns card to write id scaffolding type. + * @returns {JSX.Element} + */ + const scaffoldingInformation = () => { + return ( + <div className={"input-information"}> + <div className={"input-fields-add"}> + <p className={"input-sorting-text"}>Enter ID</p> + + <input type={"text"} className={"form-control scaffolding-input"} onChange={event => { + //Setting the id + setScaffolding({...scaffolding, id: event.target.value}) + }}/> + + </div> + <div className={"input-fields-add"}> + <p className={"input-sorting-text"}>Stillasdel:</p> + <select + className={"form-select scaffolding-input"} + value={scaffolding.type} + onChange={(e) => { + //setting the type + setScaffolding({...scaffolding, type: e.target.value}) + }}> + <option value={"Bunnskrue"}>Bunnskrue</option> + <option value={"Spir"}>Spir</option> + <option value={"Diagonalstang"}>Diagonalstang</option> + <option value={"Enrørsbjelke"}>Enrørsbjelke</option> + <option value={"Lengdebjeke"}>Lengdebjeke</option> + <option value={"Plank"}>Plank</option> + <option value={"Gelender"}>Gelender</option> + <option value={"Rekkverksramme"}>Rekkverksramme</option> + <option value={"Stillaslem"}>Stillaslem</option> + <option value={"Trapp"}>Trapp</option> + </select> + </div> + </div> + ) + } + + + const [type, setType] = useState("") + + + /** + * Function that will add the new scaffolding object + * + * @returns {Promise<void>} + */ + const postRequest = async () => { + setButtonPress(true) + const body = [ + scaffolding + ] + try { + await postModel(SCAFFOLDING_URL, (body)) + setType("success") + + } catch (e) { + setType("danger") + } + } + + return ( + <div className={"main-add-scaffolding"}> + {(type === "success") ? + (<Alert className={"alert-success"} + key={"success"} variant={"success"}> + Stillasdel har blitt registrert + </Alert>) : null} + {(type === "danger" && buttonPress) ? + (<Alert className={"alert-success"} + key={"danger"} variant={"danger"}> + Stillasdel har ikke blitt registrert + </Alert>) : null} + <div className={"info-card"}> + {scaffoldingInformation()} + <div className={"btn-add-scaffolding"}> + <button className={"btn"} onClick={() => postRequest()}>Legg til</button> + </div> + </div> + </div> + ) + + +} + +export default AddScaffolding diff --git a/webstillas/src/Pages/mapPage.js b/webstillas/src/Pages/mapPage.js new file mode 100644 index 0000000000000000000000000000000000000000..2a8e2d7d67c5072d29bcbf450bbbd67eaf26e254 --- /dev/null +++ b/webstillas/src/Pages/mapPage.js @@ -0,0 +1,87 @@ +import React from "react"; +import "../Assets/Styles/mapPage.css" +import {MAP_STYLE_V11, PROJECTS_WITH_SCAFFOLDING_URL} from "../Constants/apiURL"; +import {GetCachingData} from "../Middleware/addData"; +import ReactMapboxGl, {ScaleControl, Marker, ZoomControl} from "react-mapbox-gl"; +import {MapBoxAPIKey} from "../Config/firebaseConfig"; +import img from "../Assets/Images/marker.png" +import {InternalServerError} from "../components/Indicators/error"; +import {SpinnerDefault} from "../components/Indicators/Spinner"; + +const Map = ReactMapboxGl({ + accessToken: MapBoxAPIKey +}); + + +//Kode hentet fra https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/ +/** + * Function that will display a map on the website, with markers on lat, long. + * + * @param props information of project + * @returns {JSX.Element} + */ +function MapPageClass(props) { + + const projectData = props.data + const lng = 10.69155 + const lat = 60.79574 + + const onClick = (data) => { + window.alert(data.projectName) + } + + //Returns a map centered at desired longitude and latitude. + return ( + <Map + style={MAP_STYLE_V11} + containerStyle={{ + height: '100vh', + width: '100vw' + }} + center={[lng, lat]} + > + + + {projectData.map(res => { + return ( + <Marker + key = {res.projectID} + offsetTop={-48} + offsetLeft={-24} + coordinates={[res.longitude, res.latitude]} + onClick={() => onClick(res)} + > + <img src={img} alt={""}/> + </Marker> + ) + })} + + <ZoomControl + position="top-right" + /> + + <ScaleControl/> + </Map> + + ); + +} + + +/** + * Function that will fetch the information from API/Cache. + * If loading a spinner will be displayed. + * + * @returns {JSX.Element} + */ +export const MapPage = () => { + const {isLoading, data, isError} = GetCachingData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) + if (isLoading) { + return <SpinnerDefault /> + } else if(isError){ + return <InternalServerError /> + } else { + const projects = JSON.parse(data.text) + return <MapPageClass data={projects}/> + } +} diff --git a/webstillas/src/Pages/projects.js b/webstillas/src/Pages/projects.js new file mode 100644 index 0000000000000000000000000000000000000000..ef9f8ca68ea8ef093f5d364018196764b225f5eb --- /dev/null +++ b/webstillas/src/Pages/projects.js @@ -0,0 +1,193 @@ +import React, {useState} from "react"; +import "../Assets/Styles/projects.css" +import CardElement from '../components/projects/mainProjectCard' +import {Route, Routes} from "react-router-dom"; +import {PROJECTS_WITH_SCAFFOLDING_URL} from "../Constants/apiURL"; +import {GetCachingData} from "../Middleware/addData"; +import {SpinnerDefault} from "../components/Indicators/Spinner"; +import {InternalServerError} from "../components/Indicators/error"; + + +/** + Class that will create an overview of the projects + */ +export function Project() { + const [fromSize, setFromSize] = useState(0) + const [toSize, setToSize] = useState(0) + + const [searchName, setSearchName] = useState("") + const [selectedOption, setSelectedOption] = useState("") + + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + + + const {isLoading, data, isError} = GetCachingData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) + + + //If data is loading + if (isLoading) { + return ( + <SpinnerDefault/> + ) + } else if (isError) //If an Indicators while fetching data has occurred + { + return <InternalServerError/> + } else { + const allProjects = (JSON.parse(data.text)) + return ( + <div className={"main-project-window"}> + + <div className={"main-sidebar"}> + <div className={"search-filter"}> + <p className={"input-sorting-text"}>Status</p> + <select className={"form-select options"} onChange={(e) => + setSelectedOption(e.target.value)}> + <option defaultValue="">Velg her</option> + <option value={"Active"}>Aktiv</option> + <option value={"Inactive"}>Inaktiv</option> + <option value={"Upcoming"}>Kommende</option> + </select> + </div> + <div className={"search-filter"}> + <p className={"input-sorting-text"}>Prosjekt navn: </p> + <input + className={"form-control"} + type="text" + placeholder={"Søk prosjekt navn"} + value={searchName} + onChange={e => { + setSearchName(e.target.value) + }}/> + </div> + <div className={"search-filter"}> + <p className={"input-sorting-text"}>Stillsmengde: </p> + <div className={"search-filter size"}> + <input + className={"form-control size-search"} + type="number" + placeholder={"Fra"} + min={0} + onWheel={(e) => e.prototype} + onChange={e => setFromSize(Number(e.target.value))} + /> + <input + className={"form-control size-search"} + type="number" + placeholder={"Til"} + min={0} + onChange={e => { + setToSize(Number(e.target.value)) + }} + /> + </div> + </div> + <div className={"date-filter"}> + <p className={"input-sorting-text"}>Fra dato: </p> + <input + className={"form-control"} + type="date" + onChange={e => { + setStartDate(formatDateToString(e.target.value)) + }}/> + </div> + <div className={"search-filter"}> + <p className={"input-sorting-text"}>Til dato: </p> + <input + className={"form-control"} + type="date" + onChange={e => { + setEndDate(formatDateToString(e.target.value)) + }}/> + </div> + </div> + <div> + <div className={"projects-display"}> + {allProjects.filter(data => (data.projectName.toLowerCase()).includes(searchName.toLowerCase())) + .filter(data => { + if (fromSize !== 0) { + console.log(fromSize) + return data.size > fromSize + } else { + return true + } + }) + .filter(data => { + console.log(startDate) + if (startDate !== null) { + return formatDate(data.period.startDate) >= formatDate(startDate) + } else { + return true + } + }) + .filter(data => { + if (endDate !== null) { + return formatDate(data.period.endDate) <= formatDate(endDate) + } else { + return true + } + }) + .filter(data => { + if (toSize !== 0) { + return data.size < toSize + } else { + return true + } + }) + .filter(data => { + if (!(selectedOption.length === 0) && !(selectedOption === "Velg her")) { + return data.state === selectedOption + } else { + return true + } + }) + .map((e) => { + return ( + <div key={e.projectID}> + <Routes> + <Route path="/project/:id" element={<CardElement data={e}/>}/> + </Routes> + <CardElement key={e.projectID} + id={e.projectID} + name={e.projectName} + state={e.state} + rentPeriod={e.period.startDate} + size={e.size} + contactPerson={e.customer.name} + contactNumber={e.customer.number} + address_Street={e.address.street} + address_Municipality={e.address.municipality} + address_zip={e.address.zipcode} + /> + </div> + ); + })} + + </div> + </div> + </div> + + ); + } +} + + +/** + * Function to format date from "mm-dd-yyyy" to "dd-mm-yyyy" + * @param inputDate in format "mm-dd-yyyy" + * @returns {Date} in format "dd-mm-yyyy" + */ +export function formatDate(inputDate) { + const dateArray = inputDate.split('-') + return new Date(dateArray[2] + '-' + dateArray[1] + '-' + dateArray[0]) +} + +/** + * Function to format date from "mm-dd-yyyy" to "dd-mm-yyyy" + * @param inputDate in format "mm-dd-yyyy" + * @returns {string} in format "dd-mm-yyyy" + */ +export function formatDateToString(inputDate) { + const dateArray = inputDate.split('-') + return (dateArray[2] + '-' + dateArray[1] + '-' + dateArray[0]) +} diff --git a/webstillas/src/components/scaffolding/scaffolding.js b/webstillas/src/Pages/scaffolding.js similarity index 59% rename from webstillas/src/components/scaffolding/scaffolding.js rename to webstillas/src/Pages/scaffolding.js index a58682c0b1200b1441ef13db6073f9a88314c4a8..b16ef2a33033e5cbc177d85dd9739be0b7362b46 100644 --- a/webstillas/src/components/scaffolding/scaffolding.js +++ b/webstillas/src/Pages/scaffolding.js @@ -1,15 +1,14 @@ import React from "react"; -import "./scaffolding.css" -import CardElement from "./elements/scaffoldingCard"; -import {PROJECTS_WITH_SCAFFOLDING_URL, SCAFFOLDING_URL, STORAGE_URL} from "../../modelData/constantsFile"; -import {GetDummyData} from "../../modelData/addData"; -import {useQueryClient} from "react-query"; -import {SpinnerDefault} from "../Spinner"; +import "../Assets/Styles/scaffolding.css" +import CardElement from "../components/scaffolding/scaffoldingCard"; +import {PROJECTS_WITH_SCAFFOLDING_URL, SCAFFOLDING_URL, STORAGE_URL} from "../Constants/apiURL"; +import {GetCachingData} from "../Middleware/addData"; +import {SpinnerDefault} from "../components/Indicators/Spinner"; +import {InternalServerError} from "../components/Indicators/error"; /** Class that will create an overview of the scaffolding parts */ - class ScaffoldingClass extends React.Component { constructor(props) { super(props); @@ -24,19 +23,25 @@ class ScaffoldingClass extends React.Component { } + /** + * Function that will count numbers of occurrences different types of scaffolding. + * + * @param arr is the array we are iterating. + * @param key is the field we want to count. + * @returns {*[]} + */ countObjects(arr, key){ let arr2 = []; - arr.forEach((x)=>{ + arr?.forEach((x)=>{ // Checking if there is any object in arr2 // which contains the key value - if(arr2.some((val)=>{return val[key] === x[key]})){ + if(arr2?.some((val)=>{return val[key] === x[key]})){ // If yes! then increase the occurrence by 1 - arr2.forEach((k)=>{ + arr2?.forEach((k)=>{ if(k[key] !== x[key]){ k["occurrence"]++ } }) - }else{ // If not! Then create a new object initialize // it with the present iteration key's value and @@ -44,7 +49,7 @@ class ScaffoldingClass extends React.Component { let a = {} a[key] = x[key] a["occurrence"] = 1 - arr2.push(a); + arr2?.push(a); } }) @@ -52,14 +57,21 @@ class ScaffoldingClass extends React.Component { } + /** + * Function to add occurrences of type scaffolding in desired body. + * + * @param scaffold count of occurrences + * @param storage objects in an array. + * @returns {{scaffolding: *[]}} + */ scaffoldingAndStorage(scaffold, storage){ const scaffoldVar = { scaffolding: [] }; - for(var scaffoldIndex of scaffold) { - var scaff = scaffoldIndex; - for (var storageIndex of storage){ - var stor = storageIndex; + for(const scaffoldIndex of scaffold) { + const scaff = scaffoldIndex; + for (const storageIndex of storage){ + const stor = storageIndex; if (stor.type.toLowerCase() === scaff.type.toLowerCase()){ scaffoldVar.scaffolding.push({ "type" :scaff.type, @@ -79,18 +91,17 @@ class ScaffoldingClass extends React.Component { const objectArr = this.countObjects(scaffolding, "type") const scaffoldingObject = this.scaffoldingAndStorage(objectArr, storage) - console.log(scaffoldingObject); const result = Object.keys(scaffoldingObject).map((key) => scaffoldingObject[key]); + //If user would like to sort based on scaffolding if (selectedOption === "ascending") { - result[0].sort((a, b) => (a.scaffolding < b.scaffolding) ? 1 : -1) + result[0]?.sort((a, b) => (a.scaffolding < b.scaffolding) ? 1 : -1) } else if (selectedOption === "descending") { - result[0].sort((a, b) => (a.scaffolding > b.scaffolding) ? 1 : -1) + result[0]?.sort((a, b) => (a.scaffolding > b.scaffolding) ? 1 : -1) } else { - result[0].sort((a, b) => (a.type > b.type)) + result[0]?.sort((a, b) => (a.type > b.type)) } return ( - //todo only scroll the scaffolding not the map <div className={"scaffolding"}> <div className={"all-scaffolding"}> <div className={"sorting"}> @@ -104,7 +115,7 @@ class ScaffoldingClass extends React.Component { </div> <div className={"grid-container"}> - {result[0].map((e) => { + {result[0]?.map((e) => { return ( <CardElement key={e.type} @@ -125,29 +136,33 @@ class ScaffoldingClass extends React.Component { } -export const Scaffolding = () => { - const {isLoading: LoadingScaffolding, data: Scaffolding} = GetDummyData("scaffolding", SCAFFOLDING_URL) - const {isLoading: LoadingStorage, data: Storage} = GetDummyData("storage", STORAGE_URL) - const queryClient = useQueryClient() - let LoadingAll - let ProjectsData - if (queryClient.getQueryData("allProjects") !== undefined) { - ProjectsData = queryClient.getQueryData("allProjects") - } - const {isLoading: LoadingAllProjects, data} = GetDummyData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) - ProjectsData = data - LoadingAll = LoadingAllProjects - +/** + * Function to display information about scaffolding + * @returns {JSX.Element} + * @constructor + */ +export const Scaffolding = () => { + const {isLoading: LoadingScaffolding, data: Scaffolding, isError: scaffoldingError} = GetCachingData("scaffolding", SCAFFOLDING_URL) + const {isLoading: LoadingStorage, data: Storage, isError: storageError} = GetCachingData("storage", STORAGE_URL) + const {isLoading: LoadingAll, data: Project, isError: allProjectError} = GetCachingData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) + //If loading if (LoadingScaffolding || LoadingStorage || LoadingAll) { return <SpinnerDefault /> - } else { - return <ScaffoldingClass scaffolding = {Scaffolding} - storage = {Storage} - projects = {ProjectsData} + } else if(scaffoldingError || storageError || allProjectError) //If loading Indicators + { + return <InternalServerError /> + } else { //On success + const scaffoldingData = JSON.parse(Scaffolding.text) + const storageData = JSON.parse(Storage.text) + const projectData = JSON.parse(Project.text) + + return <ScaffoldingClass scaffolding = {scaffoldingData} + storage = {storageData} + projects = {projectData} /> } } diff --git a/webstillas/src/components/userinformation/userInfo.js b/webstillas/src/Pages/userInfo.js similarity index 67% rename from webstillas/src/components/userinformation/userInfo.js rename to webstillas/src/Pages/userInfo.js index 915f56c2344b0d8ed9d6e8581ae885b8b31ffcbe..4b9588df826234cb4222801707204c7b8826022e 100644 --- a/webstillas/src/components/userinformation/userInfo.js +++ b/webstillas/src/Pages/userInfo.js @@ -1,27 +1,45 @@ import React from "react"; -import { auth } from "../../firebase"; -import {GetDummyData} from "../../modelData/addData"; -import { USER_URL} from "../../modelData/constantsFile"; -import {SpinnerDefault} from "../Spinner"; -import "./userInfo.css" -import profileImg from "./profile-png-icon-2.png" +import { auth } from "../Config/firebase"; +import {GetCachingData} from "../Middleware/addData"; +import { USER_URL} from "../Constants/apiURL"; +import {SpinnerDefault} from "../components/Indicators/Spinner"; +import "../Assets/Styles/userInfo.css" +import profileImg from "../Assets/Images/profile-png-icon-2.png" +import {InternalServerError} from "../components/Indicators/error"; + +/** + * Function that will return information about the user. + * @returns {JSX.Element} + * @constructor + */ export function UserInfo(){ - const {isLoading, data} = GetDummyData("user", USER_URL + auth.currentUser.uid) + let isLoadingUser, userData, isErrorUser + + //If user is authenticated load user data + if (auth.currentUser){ + const {isLoading, data, isError} = GetCachingData("user", USER_URL + auth.currentUser.uid) + isLoadingUser = isLoading + userData = data + isErrorUser = isError + } - if (isLoading) { + if (isLoadingUser) { return (<SpinnerDefault/>) - } else { + } else if( isErrorUser){ + return <InternalServerError /> + } + else { + const user = JSON.parse(userData.text) return ( <div className={"main-userinfo"}> <div className={"info-card"}> <div className={"image-frame"}> <img src={profileImg} alt={""} className={"profile-image"}/> - </div> <div className={"information-text"}> <h4 className={"header-information"}> - {data.name.firstName} {data.name.lastName} + {user?.name.firstName} {user?.name.lastName} </h4> <h4 className={"under-information"}> Navn @@ -29,7 +47,7 @@ export function UserInfo(){ </div> <div className={"information-text"}> <h4 className={"header-information"}> - {data.phone} + {user?.phone} </h4> <h4 className={"under-information"}> Telefonnummer @@ -37,7 +55,7 @@ export function UserInfo(){ </div> <div className={"information-text"}> <h4 className={"header-information"}> - {data.email} + {user?.email} </h4> <h4 className={"under-information"}> Email @@ -45,7 +63,7 @@ export function UserInfo(){ </div> <div className={"information-text"}> <h4 className={"header-information"}> - {data.employeeID} + {user?.employeeID} </h4> <h4 className={"under-information"}> Ansatt ID @@ -53,7 +71,7 @@ export function UserInfo(){ </div> <div className={"information-text"}> <h4 className={"header-information"}> - {data.dateOfBirth} + {user?.dateOfBirth} </h4> <h4 className={"under-information"}> Fødselsdato @@ -61,7 +79,7 @@ export function UserInfo(){ </div> <div className={"information-text"}> <h4 className={"header-information"}> - {data.role} + {user?.role} </h4> <h4 className={"under-information"}> Stilling @@ -69,7 +87,7 @@ export function UserInfo(){ </div> <div className={"information-text"}> <h4 className={"header-information"}> - {data.admin.toString()} + {user?.admin.toString()} </h4> <h4 className={"under-information"}> Administrerende rettigheter diff --git a/webstillas/src/Routes/App.js b/webstillas/src/Routes/App.js new file mode 100644 index 0000000000000000000000000000000000000000..567b8eca210a52a59ed964158a01b15025c2ac3a --- /dev/null +++ b/webstillas/src/Routes/App.js @@ -0,0 +1,64 @@ +import '../Assets/Styles/App.css'; +import React from "react"; +import {Routes, Route} from "react-router-dom"; +import {Project} from "../Pages/projects"; +import {MapPage} from "../Pages/mapPage"; +import {Scaffolding} from "../Pages/scaffolding"; +import TopBar from "../Layout/topBar/topBar"; +import {PreViewSite} from "../components/projects/preViewSite"; +import {QueryClientProvider, QueryClient} from 'react-query' +import {ReactQueryDevtools} from 'react-query/devtools' +import ProtectedRoute from "./ProtectedRoute"; +import Login from "../Pages/Login"; +import Signup from "../Pages/Signup"; +import {UserAuthContextProvider} from "../Config/UserAuthContext"; +import AddProjectFunc from "../components/addproject/addProject"; +import AddScaffolding from "../Pages/addScaffolding"; +import {UserInfo} from "../Pages/userInfo"; +import {NotFound} from "../components/Indicators/error"; +import { + ADD_PROJECT_URL, ADD_SCAFFOLDING_URL, LOGIN, + MAP_URL, NOTFOUND, + PROJECT_URL, + PROJECT_URL_ID, + SCAFFOLDING_URL, SIGNUP, USERINFO_URL +} from "../Constants/webURL"; + + +const queryClient = new QueryClient() + + +/** + * Function that will route the + * @returns {JSX.Element} + * @constructor + */ +function App() { + return ( + //Authorisation of user + <UserAuthContextProvider> + {/*Caching provider client*/} + <QueryClientProvider client={queryClient}> + <TopBar/> {/*Topbar for the user to navigate throughout the webpage}*/} + <Routes> {/*Router that creates the routes the user is able to navigate*/} + <Route path={PROJECT_URL} exact={true} element={<ProtectedRoute> <Project/></ProtectedRoute>}/> + <Route path={MAP_URL} exact={true} element={<ProtectedRoute> <MapPage/></ProtectedRoute>}/> + <Route path={SCAFFOLDING_URL} exact={true} element={<ProtectedRoute> <Scaffolding/></ProtectedRoute>}/> + <Route path={PROJECT_URL_ID} exact={true} element={<ProtectedRoute> <PreViewSite/></ProtectedRoute>}/> + <Route path={LOGIN} exact={true} element={<Login/>}/> + <Route path={SIGNUP} exact={true} element={<Signup/>}/> + <Route path={ADD_PROJECT_URL} exact={true} + element={<ProtectedRoute> <AddProjectFunc/></ProtectedRoute>}/> + <Route path={ADD_SCAFFOLDING_URL} exact={true} + element={<ProtectedRoute> <AddScaffolding/></ProtectedRoute>}/> + <Route path={USERINFO_URL} exact={true} element={<ProtectedRoute> <UserInfo/></ProtectedRoute>}/> + <Route path={NOTFOUND} element={<NotFound/>}/> + </Routes> + <ReactQueryDevtools initialIsOpen={true}/> + </QueryClientProvider> + </UserAuthContextProvider> + + ); +} + +export default App; diff --git a/webstillas/src/components/ProtectedRoute.js b/webstillas/src/Routes/ProtectedRoute.js similarity index 56% rename from webstillas/src/components/ProtectedRoute.js rename to webstillas/src/Routes/ProtectedRoute.js index 3cc9297bb608528265c4d72def5284f646633bbc..fec81fa9af1c76c9017e45773c1a981ab64782ca 100644 --- a/webstillas/src/components/ProtectedRoute.js +++ b/webstillas/src/Routes/ProtectedRoute.js @@ -1,6 +1,13 @@ import React from "react"; import { Navigate } from "react-router-dom"; -import { useUserAuth } from "../context/UserAuthContext"; +import { useUserAuth } from "../Config/UserAuthContext"; + +/** + * Function that will check if the user is authenticated, before sending the user to a protected route + * + * @param children the element route. + * @returns {JSX.Element|*} + */ const ProtectedRoute = ({ children }) => { const { user } = useUserAuth(); diff --git a/webstillas/src/components/Spinner.js b/webstillas/src/components/Indicators/Spinner.js similarity index 70% rename from webstillas/src/components/Spinner.js rename to webstillas/src/components/Indicators/Spinner.js index 59b2dec4b3205773ce66b5cad92ebc84955c2a85..9ce4cca4a06b2899351731a65cd43c303581b800 100644 --- a/webstillas/src/components/Spinner.js +++ b/webstillas/src/components/Indicators/Spinner.js @@ -1,6 +1,13 @@ import {Spinner} from "react-bootstrap"; import React from "react"; +import "../../Assets/Styles/Spinner.css" +/** + * Function that will return a spinner + * + * @returns {JSX.Element} + * @constructor + */ export const SpinnerDefault = () =>{ return( <Spinner className={"spinner"} animation="border" style={{ width: '5rem', height: '5rem' }} role="status"> diff --git a/webstillas/src/components/Indicators/error.js b/webstillas/src/components/Indicators/error.js new file mode 100644 index 0000000000000000000000000000000000000000..398a55bc83c395552b258674f6709acf20fa45bb --- /dev/null +++ b/webstillas/src/components/Indicators/error.js @@ -0,0 +1,26 @@ +/* +Error function that will be displayed if the user requests an invalid url. + */ +export function NotFound() { + return ( + <h1> + 404 Page Not Found + </h1> + ) +} + +/* +Error function that will be displayed if a server Indicators occurs. + */ +export function InternalServerError() { + return ( + <h1> + 500 Internal Server Error + </h1> + ) +} + + +export function AlertCatch(){ + window.alert("En feil oppstod! Prøv igjen senere.") +} diff --git a/webstillas/src/components/Signup.js b/webstillas/src/components/Signup.js deleted file mode 100644 index aa6bdcdda99638330bb16cd00e2bbc3b1b7718b3..0000000000000000000000000000000000000000 --- a/webstillas/src/components/Signup.js +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useState } from "react"; -import { Link, useNavigate } from "react-router-dom"; -import { Form, Alert } from "react-bootstrap"; -import { Button } from "react-bootstrap"; -import { useUserAuth } from "../context/UserAuthContext"; -import postModel from "../modelData/postModel"; -import {formatDate, formatDateToString} from "./projects/projects"; - -const Signup = () => { - const [email, setEmail] = useState(""); - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [role, setRole] = useState(""); - const [phone, setPhone] = useState(0); - const [admin, setAdmin] = useState(false); - const [birthDay, setBirthDay] = useState(""); - - const [error, setError] = useState(""); - const [password, setPassword] = useState(""); - const { signUp } = useUserAuth(); - - let navigate = useNavigate(); - - const handleSubmit = async (e) => { - e.preventDefault(); - setError(""); - try { - signUp(email, password).then(newUser => { - console.log(newUser.user.uid) - const user = - { - "employeeID": newUser.user.uid, - "name": { - "firstName": firstName, - "lastName": lastName - }, - "role": role, - "phone": phone, - "email": email, - "admin": admin, - "dateOfBirth": birthDay - } - console.log(JSON.stringify(user)) - postModel("user", user) - .then(() => navigate("/")) - .catch(e => console.log(e)) - }) - } catch (err) { - setError(err.message); - } - }; - - - - - - console.log(birthDay) - - return ( - <> - <div className="p-4 box"> - <h2 className="mb-3">Firebase Auth Signup</h2> - {error && <Alert variant="danger">{error}</Alert>} - <Form onSubmit={handleSubmit}> - <Form.Group className="mb-3" controlId="firstName"> - <Form.Control - type="text" - placeholder="First name" - onChange={(e) => setFirstName(e.target.value)} - /> - </Form.Group> - <Form.Group className="mb-3" controlId="lastName"> - <Form.Control - type="text" - placeholder="Last name" - onChange={(e) => setLastName(e.target.value)} - /> - </Form.Group> - - <Form.Group className="mb-3" controlId="lastName"> - <Form.Control - type="number" - placeholder="phone" - onChange={(e) => setPhone(Number(e.target.value))} - /> - </Form.Group> - - <Form.Group className="mb-3" controlId="admin"> - <Form.Select onChange={(e) => setAdmin(Boolean(e.target.value))}> - <option value={"false"}>False</option> - <option value={"true"}>True</option> - </Form.Select> - </Form.Group> - - <Form.Group className="mb-3" controlId="role"> - <Form.Select onChange={(e) => setRole(e.target.value)}> - <option value={"admin"}>Administrator</option> - <option value={"installer"}>Installatør</option> - <option value={"storage"}>Lagerarbeider</option> - - </Form.Select> - </Form.Group> - - <label htmlFor="startDate">Birthday</label> - <input type="date" onChange={(event) => setBirthDay(formatDateToString(event.target.value))}/> - - <Form.Group className="mb-3" controlId="formBasicEmail"> - <Form.Control - type="email" - placeholder="Email address" - onChange={(e) => setEmail(e.target.value)} - /> - </Form.Group> - <Form.Group className="mb-3" controlId="formBasicPassword"> - <Form.Control - type="password" - placeholder="Password" - onChange={(e) => setPassword(e.target.value)} - /> - </Form.Group> - <div className="d-grid gap-2"> - <Button variant="primary" type="Submit"> - Sign up - </Button> - </div> - </Form> - </div> - <div className="p-4 box mt-3 text-center"> - Already have an account? <Link to="/">Log In</Link> - </div> - </> - ); -}; - -export default Signup; diff --git a/webstillas/src/components/Spinner.css b/webstillas/src/components/Spinner.css deleted file mode 100644 index d50fc6d702eadb923021b0d3e3d51cfcea4a5c73..0000000000000000000000000000000000000000 --- a/webstillas/src/components/Spinner.css +++ /dev/null @@ -1,5 +0,0 @@ - -.spinner{ - display: flex; - justify-content: center; -} diff --git a/webstillas/src/components/logistics/project/addProject.js b/webstillas/src/components/addproject/addProject.js similarity index 54% rename from webstillas/src/components/logistics/project/addProject.js rename to webstillas/src/components/addproject/addProject.js index d795e484864e2a536dd4316e7b24331aa415c587..4005a4945e2453c2445add7c50f1d88eccb9e789 100644 --- a/webstillas/src/components/logistics/project/addProject.js +++ b/webstillas/src/components/addproject/addProject.js @@ -1,38 +1,41 @@ -import React, {useCallback, useRef, useState} from 'react' -import {MapClass} from "./map"; +import React, {useState} from 'react' +import {MapClass} from "../../Pages/addProjectMap"; import MapboxAutocomplete from "react-mapbox-autocomplete"; import 'mapbox-gl/dist/mapbox-gl.css' -import "./addProject.css" +import "../../Assets/Styles/addProject.css" import {Alert} from "react-bootstrap"; +import {MapBoxAPIKey} from "../../Config/firebaseConfig"; -export default function AddProjectFunc() { +/** + Function that will allow the user to add a new project to the system. + */ +export default function AddProjectFunc() { + //Access token to the mapbox api. const mapAccess = { // Thanks to SomeSoftwareTeam (https://github.com/SomeSoftwareTeam/some-react-app/blob/acd17860b8b1f51edefa4e18486cc1fb07afff70/src/components/SomeComponent.js) - mapboxApiAccessToken: - "pk.eyJ1IjoiZmFrZXVzZXJnaXRodWIiLCJhIjoiY2pwOGlneGI4MDNnaDN1c2J0eW5zb2ZiNyJ9.mALv0tCpbYUPtzT7YysA2g" + mapboxApiAccessToken: MapBoxAPIKey }; - + /* + Initialise variables the user must fill in order to add a new project. + */ const [address, setAddress] = useState({street: "", zipcode: 0, municipality: "", county: ""}) - - const [period, setPeriod] = useState({startDate: "", endDate: ""}) - const [customer, setCustomer] = useState({name: "", email: ""}) - const [customerNumber, setCustomerNumber] = useState({number: 0}) - const [projectDetails, setProjectDetails] = useState({ projectID: Math.round(Math.random() * 1000000), projectName: '', latitude: 0, longitude: 0, - state: "Inactive" + state: "" }) - const [size, setSize] = useState({size: 0}) + /* + Error messages that will be displayed to the user, if the user has entered invalid input. + */ const [errors, setErrors] = useState({ projectName: '', address: "", @@ -43,6 +46,9 @@ export default function AddProjectFunc() { date: "" }) + /* + Checks if the input is valid. + */ const [valid, setValid] = useState({ projectNameValid: false, streetValid: false, @@ -57,6 +63,11 @@ export default function AddProjectFunc() { }) + /** + * Function that will handle and check the user input + * + * @param e the input field name and value. + */ const handleUserInputProjectDetails = (e) => { const name = e.target.name; const value = e.target.value; @@ -69,6 +80,11 @@ export default function AddProjectFunc() { setProjectDetails({...projectDetails, [name]: value}); } + /** + * Function that will handle and check the user input + * + * @param e the input field name and value. + */ const handleUserInputCustomer = (e) => { const name = e.target.name; const value = e.target.value; @@ -80,43 +96,24 @@ export default function AddProjectFunc() { setCustomer(({...customer, [name]: value})); } - const handleUserInputAddress = (e) => { - const name = e.target.name; - const value = e.target.value; - validateFieldProjectAddress(name, value) - setAddress({...address, [name]: value}); - } - - + /** + * Function that will format input from "input date" to required API format + * + * @param date as "input date" format "mm-dd-yyyy" + * @returns {string} return to API format "dd-mm-yyyy" + */ const dateFormat = (date) => { const dateArray = date.split('-') return dateArray[2] + '-' + dateArray[1] + '-' + dateArray[0] } - const validateFieldProjectAddress = (field, value) => { - switch (field) { - case "street": - setValid({...valid, streetValid: value.length > 3}) - setErrors({...errors, street: (valid.streetValid ? '' : ' is too short')}) - break; - case "zipcode": - setValid({...valid, zipcodeValid: ((value.length) === 4)}) - setErrors({...errors, zipcode: (valid.zipcodeValid ? '' : ' needs to be of length 4')}); - break; - case 'municipality': - setValid({...valid, municipalityValid: (value !== undefined)}) - setErrors({...errors, municipalityErr: (valid.municipalityValid ? '' : 'Not a valid municipality')}); - - break; - case 'county': - setValid({...valid, countyValid: (value !== undefined)}) - setErrors({...errors, countyErr: (valid.countyValid ? '' : 'Not a valid county')}); - break; - default: - break; - } - } - + /** + * Function that will validate the customer input + * If input is not valid, then a predefined Indicators message is set. + * + * @param fieldName is the object field that is going to be set + * @param value is the object value to be set. + */ const validateFieldProjectCustomer = (fieldName, value) => { switch (fieldName) { case 'name': @@ -140,6 +137,13 @@ export default function AddProjectFunc() { } } + /** + * Function to validate date format. + * If input is not valid, then a predefined Indicators message is set. + * + * @param fieldName is the object field that is going to be set + * @param value is the object value to be set. + */ const validateFieldDate = (fieldName, value) => { switch (fieldName) { case 'startDate': @@ -155,6 +159,13 @@ export default function AddProjectFunc() { } } + /** + * Function to validate projectDetails + * If input is not valid, then a predefined Indicators message is set. + * + * @param fieldName is the object field that is going to be set + * @param value is the object value to be set. + */ const validateFieldProjectDetails = (fieldName, value) => { switch (fieldName) { case 'projectName': @@ -171,6 +182,11 @@ export default function AddProjectFunc() { } + /** + * Function that will set period fields + * + * @param e is value and name of input fields. + */ const handleUserInputPeriod = (e) => { const name = e.target.name; const value = e.target.value; @@ -180,109 +196,129 @@ export default function AddProjectFunc() { } - const [mapPage, setMapPage] = useState(false) - - const nextPage = () => { - setMapPage(true) - - } + // Query that will only allow norwegian addresses const queryParams = { country: "no", place_type: "address" }; - const parseReverseGeo = async (geoData, lat, long) => { + /** + * Function that will fetch data of a spesific longitude and latitude, to set address/poi, postcode, county and municipality + * + * @param lat latitude of the place we would like to get information + * @param long longitude of the place we would like to get information + * @returns {Promise<void>} + */ + const parseReverseGeo = async (lat, long) => { let street, postcode, region, place - await fetch("https://api.mapbox.com/geocoding/v5/mapbox.places/" + long + "," + lat + ".json?access_token=pk.eyJ1IjoiYWxla3NhYWIxIiwiYSI6ImNrbnFjbms1ODBkaWEyb3F3OTZiMWd6M2gifQ.vzOmLzHH3RXFlSsCRrxODQ") - .then(res => res.json()) - .then(res => { - - let validStreet, validZip, validCounty, validMunicipality - for (const re of res.features) { - console.log((re.place_type[0])) - switch (re.place_type[0]) { - - case "address": { - street = re.text - if ((re.text.length > 3)) { - validStreet = true - } - } - break; - case "poi": { - street = re.text - if ((re.text.length > 3)) { - validStreet = true + try { + await fetch("https://api.mapbox.com/geocoding/v5/mapbox.places/" + long + "," + lat + ".json?access_token=pk.eyJ1IjoiYWxla3NhYWIxIiwiYSI6ImNrbnFjbms1ODBkaWEyb3F3OTZiMWd6M2gifQ.vzOmLzHH3RXFlSsCRrxODQ") + .then(res => res.json()) + .then(res => { + let validStreet, validZip, validCounty, validMunicipality + for (const re of res.features) { + console.log((re.place_type[0])) + switch (re.place_type[0]) { + case "address": { + street = re.text + if ((re.text.length > 3)) { + validStreet = true + } } + break; + case "poi": { + street = re.text + if ((re.text.length > 3)) { + validStreet = true + } - } - break; - case "postcode": { - postcode = re.text - if ((re.text.length === 4)) { - validZip = true } + break; + case "postcode": { + postcode = re.text + if ((re.text.length === 4)) { + validZip = true + } - } - break; - case ("region"): { - region = re.text - if ((re.text.length !== undefined)) { - validCounty = true } - } - break; - case ("place") : { - place = re.text - if ((re.text.length !== undefined)) { - validMunicipality = true + break; + case ("region"): { + region = re.text + if ((re.text.length !== undefined)) { + validCounty = true + } } + break; + case ("place") : { + place = re.text + if ((re.text.length !== undefined)) { + validMunicipality = true + } + } + break; + default: + console.log("Ikke validert") } - break; - default: - console.log("Ikke validert") - } - } + } - if (validStreet && validZip && validCounty && validMunicipality) { - setValid({ - ...valid, - countyValid: validCounty, - municipalityValid: validMunicipality, - zipcodeValid: validZip, - streetValid: validStreet - }) - if (region === "Oslo") { - setAddress({ - ...address, - street: street, - county: region, - municipality: region, - zipcode: postcode + if (validStreet && validZip && validCounty && validMunicipality) { + setValid({ + ...valid, + countyValid: validCounty, + municipalityValid: validMunicipality, + zipcodeValid: validZip, + streetValid: validStreet }) + + //If region is oslo the municipality is not set. + if (region === "Oslo") { + setAddress({ + ...address, + street: street, + county: region, + municipality: region, + zipcode: postcode + }) + } else { + setAddress({...address, street: street, county: region, municipality: place, zipcode: postcode}) + } } else { - setAddress({...address, street: street, county: region, municipality: place, zipcode: postcode}) + setErrors({...errors, address: "You have entered an invalid address"}) } - } else { - setErrors({...errors, address: "You have entered an invalid address"}) + + } + ).then(() => setProjectDetails({ + ...projectDetails, + longitude: long, + latitude: lat + })) + }catch (e) { + console.log(e) + } - } - ).then(() => setProjectDetails({ - ...projectDetails, - longitude: long, - latitude: lat - })) } - const _suggestionSelect = async (result, lat, long, country) => { - await parseReverseGeo(result, lat, long) + /** + *Function that will redirect to parseReverseGeo + * + * @param lat latitude from the suggested input of user + * @param long longitude from the suggested input of user + * @returns {Promise<void>} + */ + const _suggestionSelect = async (_, lat, long) => { + await parseReverseGeo(lat, long) } + + /** + * Struct of adding project that will set the variables the user has set. + * + */ const finalProject = { projectID: projectDetails.projectID, projectName: projectDetails.projectName, @@ -309,6 +345,11 @@ export default function AddProjectFunc() { } + /** + * Checks if all the fields are valid. + * + * @type {boolean} is set true if all the fields are valid. + */ let formsValid = false if (valid.countyValid && valid.streetValid && valid.zipcodeValid && valid.municipalityValid && valid.dateValid && valid.sizeValid && valid.projectNameValid && valid.emailValid && valid.nameValid @@ -318,19 +359,19 @@ export default function AddProjectFunc() { } - -console.log(address) - + /** + * + * @returns {JSX.Element} with input that can set the customer information. + */ const contactInformation = () => { - - return( + return ( <div> <h3>Contact Information</h3> <hr/> <div className={"input-with-text"}> <p className={"input-field-text"}>Name</p> <input - className = {"form-control"} + className={"form-control"} type={"text"} required name={"name"} @@ -338,31 +379,32 @@ console.log(address) onChange={handleUserInputCustomer} /> <p className={"error-message"}> - {(errors.name === "" || valid.nameValid) ? null: <Alert variant="danger">{errors.name}</Alert> } + {(errors.name === "" || valid.nameValid) ? null : <Alert variant="danger">{errors.name}</Alert>} </p> </div> <div className={"input-with-text"}> <p className={"input-field-text"}>Number</p> <input - className = {"form-control"} + className={"form-control"} type={"number"} - min={0} - required - name={"number"} - placeholder={"Enter Customer Number"} - onChange={handleUserInputCustomer} + min={0} + required + name={"number"} + placeholder={"Enter Customer Number"} + onChange={handleUserInputCustomer} /> <p className={"error-message"}> - {(errors.number === "" || valid.numberValid) ? null: <Alert variant="danger">{errors.number}</Alert> } + {(errors.number === "" || valid.numberValid) ? null : + <Alert variant="danger">{errors.number}</Alert>} </p> </div> <div className={"input-with-email"}> <p className={"input-field-text"}>Email</p> <input - className = {"form-control"} + className={"form-control"} type={"email"} required name={"email"} @@ -370,7 +412,8 @@ console.log(address) onChange={handleUserInputCustomer} /> <p className={"error-message"}> - {(errors.email === "" || valid.emailValid) ? null: <Alert variant="danger">{errors.email}</Alert> } + {(errors.email === "" || valid.emailValid) ? null : + <Alert variant="danger">{errors.email}</Alert>} </p> </div> @@ -379,38 +422,31 @@ console.log(address) } - - - - - console.log(valid) - - - if (!mapPage) { - return ( - <div className={"add-card"}> - <article className={"information"}> - <h1>Add project</h1> + return ( + <div className={"add-card"}> + <article className={"information"}> + <h1>Legg til prosjekt</h1> <h2>Generelt</h2> <hr/> <div> <div className={"test"}> <div className={"address-name"}> - <div className={"input-with-text"}> - <p className={"input-field-text"}>Project Name </p> - <input - className = {"form-control name"} - type={"text"} - required - name={"projectName"} - placeholder={"Project Name"} - onChange={handleUserInputProjectDetails} - /> - <p className={"error-message"}> - {(errors.projectName === "" || valid.projectNameValid) ? null: <Alert variant="danger">{errors.projectName}</Alert> } - </p> + <div className={"input-with-text"}> + <p className={"input-field-text"}>Project Name </p> + <input + className={"form-control name"} + type={"text"} + required + name={"projectName"} + placeholder={"Project Name"} + onChange={handleUserInputProjectDetails} + /> + <p className={"error-message"}> + {(errors.projectName === "" || valid.projectNameValid) ? null : + <Alert variant="danger">{errors.projectName}</Alert>} + </p> - </div> + </div> <div className={"input-with-text"}> <p className={"input-field-text"}>Project size</p> @@ -419,10 +455,11 @@ console.log(address) required name={"size"} placeholder={"Size"} - className = {"form-control number"} + className={"form-control number"} onChange={handleUserInputProjectDetails}/> <p className={"error-message"}> - {(errors.size === "" || valid.sizeValid) ? null: <Alert variant="danger">{errors.size}</Alert> } + {(errors.size === "" || valid.sizeValid) ? null : + <Alert variant="danger">{errors.size}</Alert>} </p> </div> @@ -440,7 +477,8 @@ console.log(address) className={"input-text-add"} onChange={handleUserInputPeriod}/> <p className={"error-message"}> - {(errors.date === "" || valid.dateValid) ? null: <Alert variant="danger">{errors.date}</Alert> } + {(errors.date === "" || valid.dateValid) ? null : + <Alert variant="danger">{errors.date}</Alert>} </p> @@ -454,10 +492,29 @@ console.log(address) className={"input-text-add"} onChange={handleUserInputPeriod}/> <p className={"error-message"}> - {(errors.date === "" || valid.dateValid) ? null: <Alert variant="danger">{errors.date}</Alert> } + {(errors.date === "" || valid.dateValid) ? null : + <Alert variant="danger">{errors.date}</Alert>} </p> </div> + + + <div className="col"> + <p className={"input-field-text"}>Prosjekt status</p> + <select className={"form-select options"} onChange={(e) => + setProjectDetails({...projectDetails, state: e.target.value})}> + <option defaultValue="">Velg her</option> + <option value={"Active"}>Aktiv</option> + <option value={"Inactive"}>Inaktiv</option> + <option value={"Upcoming"}>Kommende</option> + </select> + </div> + + + + + + </div> </div> <div className="col"> @@ -467,15 +524,15 @@ console.log(address) inputClass='form-control address' publicKey={mapAccess.mapboxApiAccessToken} onSuggestionSelect={_suggestionSelect} - - country="no" resetSearch={false} + country = "no" placeholder="Search Address..." queryParams={queryParams} /> <p className={"error-message"}> - {(errors.address === "" || valid.streetValid) ? null: <Alert variant="danger">{errors.address}</Alert> } + {(errors.address === "" || valid.streetValid) ? null : + <Alert variant="danger">{errors.address}</Alert>} </p> </div> </div> @@ -483,14 +540,14 @@ console.log(address) </div> </div> - </article> - <MapClass props={finalProject} - valid ={formsValid} + </article> + <MapClass props={finalProject} + valid={formsValid} + + /> + </div> + ) - /> - </div> - ) - } } diff --git a/webstillas/src/components/error/error.css b/webstillas/src/components/error/error.css deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/webstillas/src/components/error/error.js b/webstillas/src/components/error/error.js deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/webstillas/src/components/logistics/logistic.css b/webstillas/src/components/logistics/logistic.css deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/webstillas/src/components/logistics/logistic.js b/webstillas/src/components/logistics/logistic.js deleted file mode 100644 index d110f1b1de7fdc6164db072f8f3064923184adc6..0000000000000000000000000000000000000000 --- a/webstillas/src/components/logistics/logistic.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import AddProjectFunc from "./project/addProject"; -import AddScaffolding from './scaffold/addScaffolding' -import AddUser from "./user/addUser"; -import Tabs from "../projects/tabView/Tabs"; - -class Logistic extends React.Component{ - - render() { - return( - <AddProjectFunc /> - - /* <Tabs> - <div label="Legg til Prosjekt"> - <AddProjectFunc /> - </div> - <div label="Legg til Bruker"> - <AddUser /> - </div> - <div label="Leggt til Stillasdel "> - <AddScaffolding /> - </div> - </Tabs>*/ - ) - } -} - -export default Logistic diff --git a/webstillas/src/components/logistics/project/FormErrors.js b/webstillas/src/components/logistics/project/FormErrors.js deleted file mode 100644 index cfe04b561aeb239ea1929814731ace5b755bc1ba..0000000000000000000000000000000000000000 --- a/webstillas/src/components/logistics/project/FormErrors.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -export const FormErrors = ({formErrors}) => - <div className='formErrors'> - {Object.keys(formErrors).map((fieldName, i) => { - if(formErrors[fieldName].length > 0){ - return ( - <p key={i}>{fieldName} {formErrors[fieldName]}</p> - ) - } else { - return ''; - } - })} - </div> diff --git a/webstillas/src/components/logistics/scaffold/addScaffolding.js b/webstillas/src/components/logistics/scaffold/addScaffolding.js deleted file mode 100644 index c062a8c959ac6a737c7de4cabd727b323c660cee..0000000000000000000000000000000000000000 --- a/webstillas/src/components/logistics/scaffold/addScaffolding.js +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react' -import postModel from "../../../modelData/postModel"; -import {SCAFFOLDING_URL} from "../../../modelData/constantsFile"; -import "./addScaffolding.css" - -class AddScaffolding extends React.Component{ - constructor(props) { - super(props); - this.state = { - scaffolding : - { - id: 0, - type: "", - batteryLevel: 100, - location: null - }, - location: { - longitude: 0, - latitude: 0, - address: "" - } - } - } - - scaffoldingInformation(){ - return( - <div className={"input-information"}> - <div className={"input-fields-add"}> - <p className = {"input-sorting-text"}>Enter ID</p> - - <input type={"text"} className={"form-control scaffolding-input"} onChange={event => - {const scaffolding = {...this.state.scaffolding}; - scaffolding.id = Number(event.target.value); - this.setState({scaffolding})}}/> - </div> - <div className={"input-fields-add"}> - <p className = {"input-sorting-text"}>Overfør til prosjekt:</p> - <select - className={"form-select scaffolding-input"} - value={"Test"} - onChange={(e) => - {const scaffolding = {...this.state.scaffolding}; - scaffolding.type = e.target.value; - this.setState({scaffolding})}}> - <option value={"Bunnskrue"}>Bunnskrue</option> - <option value={"Spire"}>Spir</option> - <option value={"Diagonalstang"}>Diagonalstang</option> - <option value={"Enrørsbjelke"}>Enrørsbjelke</option> - <option value={"Lengdebjeke"}>Lengdebjeke</option> - <option value={"Plank"}>Plank</option> - <option value={"Rekkverksramme"}>Rekkverksramme</option> - <option value={"Stillaslem"}>Stillaslem</option> - <option value={"Trapp"}>Trapp</option> - </select> - </div> - </div> - ) - } - - postRequest(){ - this.state.scaffolding.location = this.state.location - const body = [ - this.state.scaffolding - ] - - try { - postModel(SCAFFOLDING_URL, JSON.stringify(body)) - }catch (e){ - console.log(e) - } - - } - - - render() { - return( - <div className={"main-add-scaffolding"}> - <div className={"info-card"}> - {this.scaffoldingInformation()} - <div className={"btn-add-scaffolding"}> - <button className={"btn"} onClick={() => this.postRequest()}>Legg til</button> - </div> - </div> - </div> - - - - ) - } -} - -export default AddScaffolding diff --git a/webstillas/src/components/logistics/user/addUser.css b/webstillas/src/components/logistics/user/addUser.css deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/webstillas/src/components/logistics/user/addUser.js b/webstillas/src/components/logistics/user/addUser.js deleted file mode 100644 index 9c397f54de574d923f65ee13f28b3388b91bc5ed..0000000000000000000000000000000000000000 --- a/webstillas/src/components/logistics/user/addUser.js +++ /dev/null @@ -1,244 +0,0 @@ -import React from "react"; -import {FormErrors} from "../project/FormErrors"; -import postModel from "../../../modelData/postModel"; -import {USER_URL} from "../../../modelData/constantsFile"; - -class AddUser extends React.Component{ - constructor(props) { - super(props); - this.state = { - - formsErrors: - { - firstName: "", - lastName: "", - number: 0, - email: "", - date: "", - role: "", - admin: "" - }, - - firstNameValid: false, - lastNameValid: false, - numberValid: false, - emailValid: false, - dateValid: false, - roleValid: false, - adminValid: false, - employee : { - employeeID: Math.round(Math.random() * 1000), - dateOfBirth: null, - role: "", - phone: 0, - email: "", - admin: false, - name: null - }, - name: { - firstName: "", - lastName: "" - }, - } - } - - dateFormat(date){ - const dateArray = date.split('-') - return dateArray[2] + '-' + dateArray[1] + '-' + dateArray[0] - } - - - addPostRequest(){ - this.state.employee.name = this.state.name - try { - postModel(USER_URL, JSON.stringify(this.state.employee)) - }catch (e){ - console.log(e) - } - } - - - validateField(fieldName, value) { - let fieldValidationErrors = this.state.formsErrors; - let dateValid = this.state.dateValid; - let firstNameValid = this.state.firstNameValid; - let lastNameValid = this.state.lastNameValid; - let numberValid = this.state.numberValid - let emailValid = this.state.emailValid - let roleValid = this.state.roleValid - let adminValid = this.state.adminValid - - - switch(fieldName) { - case 'startDate': - dateValid = value.startDate !== "" - fieldValidationErrors.date = dateValid ? '': 'No Valid date'; - break; - case 'firstName': - firstNameValid = value.firstName.length >= 2 - fieldValidationErrors.firstName = firstNameValid ? '': 'No valid name'; - break; - case 'lastName': - lastNameValid = value.lastName.length >= 2 - fieldValidationErrors.lastName = lastNameValid ? '': 'No valid name'; - break; - case 'number': - numberValid =(value.phone.toString().length === 8 - && (value.phone.toString().charAt(0) === "4" - || value.phone.toString().charAt(0) === "9") - ) - fieldValidationErrors.number = numberValid ? '': 'Not a valid number'; - break; - case 'email': - const validRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/; - emailValid = value.email.match(validRegex) - fieldValidationErrors.email = emailValid ? '': 'Not a valid'; - break; - case 'date': - dateValid = value.dateOfBirth !== "" - fieldValidationErrors.date = dateValid ? '': 'No Valid date'; - break; - case 'role': - roleValid = value.role !== "Choose here"; - console.log(value.role) - fieldValidationErrors.role = roleValid ? '': 'Choose role'; - break; - case 'admin': - adminValid = value.admin !== "Choose here"; - console.log(adminValid) - fieldValidationErrors.admin = adminValid ? '': 'Choose role'; - break; - default: - break; - } - this.setState({formErrors: fieldValidationErrors, - dateValid :dateValid, - firstNameValid :firstNameValid, - lastNameValid :lastNameValid, - numberValid :numberValid, - emailValid :emailValid, - roleValid :roleValid, - adminValid :adminValid - - - }, this.validateForm); - } - - - errorClass(error) { - console.log("error " + error) - return(error.length === 0 ? '' : 'has-error'); - } - - validateForm() { - this.setState({formValid: - this.state.firstNameValid - && this.state.lastNameValid - && this.state.numberValid - && this.state.emailValid - && this.state.dateValid - && this.state.roleValid - && this.state.adminValid - }); - } - - inputInformation(){ - return( - - <div className={"input-fields"}> - <FormErrors formErrors={this.state.formsErrors} /> - - <div> - <input type={"text"} className={"input-text-add"} onChange={event => - { - const name = {...this.state.name}; - name.firstName = event.target.value; - this.setState({name}) - this.validateField("firstName", name)}} - /> - <p>Enter First name</p> - </div> - - <div> - <input type={"text"} className={"input-text-add"} - onChange={event =>{ - const name = {...this.state.name}; - name.lastName = event.target.value; - this.setState({name}) - this.validateField("lastName", name)}} - /> - <p>Enter Last name</p> - </div> - <div> - <input type={"date"} className={"input-date-add"} - onChange={event => { - const employee = {...this.state.employee}; - employee.dateOfBirth = this.dateFormat(event.target.value); - this.setState({employee}) - this.validateField("date", employee) - }}/> - <p>Enter Birthdate</p> - </div> - <div> - <input type={"number"} className={"input-text-add"} onChange={event => - {const employee = {...this.state.employee}; - employee.phone = Number(event.target.value); - this.setState({employee}) - this.validateField("number", employee) - }} - /> - <p>Enter User phone number</p> - </div> - <div> - <input type={"text"} className={"input-text-add"} onChange={event => - {const employee = {...this.state.employee}; - employee.email = event.target.value; - this.setState({employee}) - this.validateField("email", employee) - }}/> - <p>Enter User Email</p> - </div> - <div> - <select onChange={(e) => - {const employee = {...this.state.employee}; - employee.role = e.target.value; - this.setState({employee}) - this.validateField("role", employee) - - }}> - <option selected disabled defaultValue="" >Choose here</option> - <option value={"admin"}>Admin</option> - <option value={"installer"}>Installer</option> - <option value={"storage"}>Storage</option> - </select> - <p>Enter Role</p> - - </div> - - <div> - <select onChange={(e) => - {const employee = {...this.state.employee}; - employee.admin = Boolean(e.target.value); - this.setState({employee}) - this.validateField("admin", employee) - }}> - <option selected disabled defaultValue={""}>Choose here</option> - <option value={"true"}>Ja</option> - <option value={"false"}>Nei</option> - </select> - <p>Skal brukeren ha admin tillatelser?</p> - </div> - <button disabled={!this.state.formValid} onClick={() => this.addPostRequest()}>Add User</button> - </div> - - ) - } - - render() { - return( - this.inputInformation() - ) - } -} - -export default AddUser diff --git a/webstillas/src/components/mapPage/mapPage.js b/webstillas/src/components/mapPage/mapPage.js deleted file mode 100644 index 4acba241763590d3935968b360c672778b397905..0000000000000000000000000000000000000000 --- a/webstillas/src/components/mapPage/mapPage.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from "react"; -import "./mapPage.css" -import {PROJECTS_WITH_SCAFFOLDING_URL} from "../../modelData/constantsFile"; -import {GetDummyData} from "../../modelData/addData"; -import ReactMapboxGl, {ScaleControl, Source, Layer, Marker, ZoomControl} from "react-mapbox-gl"; -import img from "./mapbox-marker-icon-20px-orange.png" -import {NavigationControl} from "react-map-gl"; - - -const Map = ReactMapboxGl({ - accessToken: - "pk.eyJ1IjoiYWxla3NhYWIxIiwiYSI6ImNrbnFjbms1ODBkaWEyb3F3OTZiMWd6M2gifQ.vzOmLzHH3RXFlSsCRrxODQ" -}); - - -/** - Class that will create the map-page of the application - */ -//Kode hentet fra https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/ -function MapPageClass(props) { - const projectData = props.data - const lng = 10.69155 - const lat = 60.79574 - const zoom = 9 - - - const onClick = (data) =>{ - window.alert(data.projectName) - } - - - return ( - <Map - style="mapbox://styles/mapbox/streets-v10" - containerStyle={{ - height: '100vh', - width: '100vw' - }} - center={[lng, lat]} - > - - - {projectData.map(res => { - return( - <Marker - offsetTop={-48} - offsetLeft={-24} - coordinates={[res.longitude, res.latitude]} - onClick={() => onClick(res)} - > - <img src={img} alt={""}/> - </Marker> - ) - })} - - <ZoomControl - position="top-right" - /> - - - <ScaleControl/> - </Map> - - ); - -} - - -export const MapPage = () => { - const {isLoading, data} = GetDummyData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) - if (isLoading) { - return <h1>Loading</h1> - } else { - return <MapPageClass data={data}/> - } -} diff --git a/webstillas/src/components/projects/elements/Modal.js b/webstillas/src/components/projects/elements/Modal.js deleted file mode 100644 index 5fbedcc38ec0886bf8fda049e70988571ecef2c7..0000000000000000000000000000000000000000 --- a/webstillas/src/components/projects/elements/Modal.js +++ /dev/null @@ -1,192 +0,0 @@ -import React, {useState} from "react"; -import 'bootstrap/dist/css/bootstrap.min.css'; -import {Button, Modal} from 'react-bootstrap'; -import img from "../../scaffolding/images/spirstillas_solideq_spir_klasse_5_stillas_135_1.jpg"; -import putModel from "../../../modelData/putData"; -import {TRANSFER_SCAFFOLDING} from "../../../modelData/constantsFile"; -import {useQueryClient} from "react-query"; -import "./Modal.css" - - - -//https://ordinarycoders.com/blog/article/react-bootstrap-modal -const scaffoldingMove = - [ - { - "type": "Bunnskrue", - "quantity": 0 - }, - { - "type": "Diagonalstang", - "quantity": 0 - }, - { - "type": "Enrørsbjelke", - "quantity": 0 - }, - { - "type": "Gelender", - "quantity": 0 - }, - { - "type": "Lengdebjelke", - "quantity": 0 - }, - { - "type": "Plank", - "quantity": 0 - }, - { - "type": "Rekkverksramme", - "quantity": 0 - }, - { - "type": "Spir", - "quantity": 0 - }, - { - "type": "Stillaslem", - "quantity": 0 - }, - { - "type": "Trapp", - "quantity": 0 - } - ] - - - - -export default function InfoModalFunc(props) { - const [show, setShow] = useState(false); - const handleClose = () => setShow(false); - const handleShow = () => setShow(true); - //https://codesandbox.io/s/react-week-date-view-forked-ruxjr9?file=/src/App.js:857-868 - const queryClient = useQueryClient() - - let jsonProjects - jsonProjects = queryClient.getQueryData("allProjects") - - let jsonProject = queryClient.getQueryData(["project", props.id]) - console.log(jsonProject) - const [roomRent, setRoomRent] = useState(scaffoldingMove); - const [ToProject, setToProject] = useState(""); - const [FromProject, setFromProject] = useState(""); - - - //Todo change variable - const handleroom = (e, id) => { - let result = [...roomRent]; - result = result.map((x) => { - if (x.type.toLowerCase() === id.toLowerCase()) { - const inputvalue = (e.target.value) - x.quantity = parseInt(inputvalue, 10) - return x; - } else return x; - }); - setRoomRent(result) - }; - - //todo add a note to the user if the transaction was a success or a fail. - const AddScaffold = async () => { - console.log(JSON.stringify(move)) - await putModel(TRANSFER_SCAFFOLDING, JSON.stringify(move)); - await queryClient.resetQueries(["project", props.id]).then(() => handleClose()) - } - - const move = { - "toProjectID": Number(ToProject), - "fromProjectID": Number(FromProject), - "scaffold": roomRent - } - - - const validFormat = ToProject !== FromProject - - - return ( - <> - <Button className="nextButton" onClick={handleShow}> - Overfør deler til Prosjekt - </Button> - - <Modal show={show} - onHide={handleClose} - centered - backdrop="static" - dialogClassName="modal-dialog modal-xl" - > - <Modal.Header closeButton> - <Modal.Title>Stillas Overføring</Modal.Title> - </Modal.Header> - <Modal.Body> - <div className={"scaffoldingElement-modal"}> - <div className={"transfer-options"}> - <span>Overfør til prosjekt:</span> - <select - className={"form-select"} - value={ToProject} - onChange={(e) => setToProject(e.target.value)}> - <option selected defaultValue="">Choose here</option> - <option value={0}>Storage</option> - {jsonProjects.map(e => { - return ( - <option value={e.projectID}>{e.projectName}</option> - ) - })} - </select> - </div> - <div> - <span>Overfør fra prosjekt:</span> - <select - className={"form-select"} - value={FromProject} - onChange={(e) => setFromProject(e.target.value)}> - <option selected defaultValue="">Choose here</option> - <option value={0}>Storage</option> - {jsonProjects.map(e => { - return ( - <option value={e.projectID}>{e.projectName}</option> - ) - })} - </select> - </div> - {jsonProject[0].scaffolding.map(e => { - return ( - <div className={"card"}> - <section className={"header"}> - <h3>{e.type.toUpperCase()}</h3> - </section> - <section className={"image"}> - <img className={"img"} src={require(`../../scaffolding/images/${e.type.charAt(0).toUpperCase() + e.type.slice(1)}.jpg`)} alt={""}></img> - </section> - <input - className={"form-control"} - placeholder={"Enter quantity of scaffolding parts to transfer"} - type="number" - min={0} - key={"input" + e.type} - onChange={(j) => handleroom(j, e.type)}/> - </div> - ) - } - )} - - </div> - </Modal.Body> - <Modal.Footer> - <Button variant="secondary" onClick={handleClose}> - Close - </Button> - <Button variant="primary" disabled={!validFormat} onClick={AddScaffold}> - Save Changes - </Button> - </Modal.Footer> - </Modal> - </> - ); -} - - - - diff --git a/webstillas/src/components/projects/elements/card.js b/webstillas/src/components/projects/elements/card.js deleted file mode 100644 index 36dfa1e68d157f82062742afea91456cdfc21c48..0000000000000000000000000000000000000000 --- a/webstillas/src/components/projects/elements/card.js +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react' -import 'bootstrap/dist/css/bootstrap.min.css'; -import './card.css' -import img from '../images/blog-item.jpg' -import {Link} from "react-router-dom"; -import {PROJECTS_URL} from "../../../modelData/constantsFile"; -import deleteModel from "../../../modelData/deleteProject"; -import {useQueryClient} from "react-query"; -import {IconButton} from "@material-ui/core"; -import DeleteIcon from "@material-ui/icons/Delete"; - -function CardElement(props) { - const queryClient = useQueryClient() - const DeleteProject = async () => { - if (window.confirm("Are you sure you want to delete " + props.name + "?" )){ - const deleteBody = - [ - { - id: props.id - } - ] - await deleteModel(PROJECTS_URL, (deleteBody)).catch(e => console.log(e)).then(e => console.log("success")) - await queryClient.invalidateQueries("allProjects") - } - } - - return ( - <article className={"card"}> - <div className={"name-btn"}> - <section className={"header"}> - <h3>{props.name}</h3> - </section> - <div className={"btn-delete"}> - <IconButton onClick={DeleteProject}> - <DeleteIcon style={{ fontSize: 50}} /> - </IconButton> - </div> - - </div> - - <section className={"image-project"}> - <img src={img} alt={""}/> - </section> - - <section className={"information-highlights-cta"}> - <div className={"information-highlights"}> - <ul className={"information-list"}> - <li className={"horizontal-list"}> - <div className={"highlightText"}> - <span>{props.state}</span> - </div> - <div className={"highlightText-caption"}> - <span>Status</span> - </div> - </li> - <li className={"horizontal-list"}> - <div className={"highlightText"}> - <span>{props.rentPeriod}</span> - </div> - <div className={"highlightText-caption"}> - <span>Leieperiode</span> - </div> - </li> - <li className={"horizontal-list"}> - <div className={"highlightText"}> - <span> {props.size}</span> - <span>㎡</span> - </div> - <div className={"highlightText-caption"}> - <span>Størrelse</span> - </div> - </li> - </ul> - </div> - </section> - <section className={"contact-highlights-cta"}> - <div className={"information-highlights"}> - <ul className={"contact-list"}> - <li className={"horizontal-list-contact"}> - <span className={"left-contact-text"}>Kontakt person</span> - <span className={"right-contact-text"}>{props.contactPerson}</span> - </li> - <li className={"horizontal-list-contact"}> - <span className={"left-contact-text"}>Adresse</span> - <span - className={"right-contact-text"}>{props.address_Street}, {props.address_zip} {props.address_Municipality}</span> - </li> - <li className={"horizontal-list-contact"}> - <span className={"left-contact-text"}>Nummer</span> - <span className={"right-contact-text"}>{props.contactNumber}</span> - </li> - </ul> - </div> - </section> - <section className={"card-btn"}> - <div className={"card-btns"}> - <Link className={"btn"} to={"/project/" + props.id}>Mer Informasjon</Link> - </div> - </section> - </article> - ) -} - -export default CardElement diff --git a/webstillas/src/components/projects/images/stillas.jpg b/webstillas/src/components/projects/images/stillas.jpg deleted file mode 100644 index 71796323d7e7ec91ef3034fcc6776cf73682af96..0000000000000000000000000000000000000000 Binary files a/webstillas/src/components/projects/images/stillas.jpg and /dev/null differ diff --git a/webstillas/src/components/projects/mainProjectCard.js b/webstillas/src/components/projects/mainProjectCard.js new file mode 100644 index 0000000000000000000000000000000000000000..1d36533ca40dc833a8b6ee5216912c78c3bbfe7a --- /dev/null +++ b/webstillas/src/components/projects/mainProjectCard.js @@ -0,0 +1,111 @@ +import React from 'react' +import 'bootstrap/dist/css/bootstrap.min.css'; +import '../../Assets/Styles/card.css' +import img from '../../Assets/Images/scaffoldingimg.jpg' +import {Link} from "react-router-dom"; +import {PROJECTS_URL} from "../../Constants/apiURL"; +import deleteModel from "../../Middleware/deleteProject"; +import {useQueryClient} from "react-query"; +import {IconButton} from "@material-ui/core"; +import DeleteIcon from "@material-ui/icons/Delete"; + + +/** + * Function that will display an information card, with information of a project. + * + * @param props data sent from another view. + * @returns {JSX.Element} + */ +function CardElement(props) { + const queryClient = useQueryClient() + + /** + * Function that will delete an existing project. + * + * @returns {Promise<void>} + */ + const DeleteProject = async () => { + if (window.confirm("Er du sikker på at du vil slette? " + props.name + "?")) { + const deleteBody = + [ + { + id: props.id + } + ] + try { + await deleteModel(PROJECTS_URL, (deleteBody)).then(() => + window.alert("Prosjektet er slettet") + ) + await queryClient.invalidateQueries("allProjects") + } catch (e) { + window.alert("Noe gikk galt! Prøv igjen senere") + } + } + } + + + return ( + <div className={"card"}> + <div className={"name-btn"}> + <section className={"header"}> + <h3>{props.name}</h3> + </section> + <IconButton onClick={DeleteProject}> + <DeleteIcon style={{fontSize: 50}}/> + </IconButton> + </div> + <img className={"image-project"} src={img} alt={""}/> + <div className={"information-highlights"}> + <ul className={"information-list"}> + <li className={"horizontal-list"}> + <div className={"highlightText"}> + <span>{props.state}</span> + </div> + <div className={"highlightText-caption"}> + <span>Status</span> + </div> + </li> + <li className={"horizontal-list"}> + <div className={"highlightText"}> + <span>{props.rentPeriod}</span> + </div> + <div className={"highlightText-caption"}> + <span>Leieperiode</span> + </div> + </li> + <li className={"horizontal-list"}> + <div className={"highlightText"}> + <span> {props.size}</span> + <span>㎡</span> + </div> + <div className={"highlightText-caption"}> + <span>Størrelse</span> + </div> + </li> + </ul> + </div> + <div className={"information-highlights"}> + <ul className={"contact-list"}> + <li className={"horizontal-list-contact"}> + <span className={"left-contact-text"}>Kontakt person</span> + <span className={"right-contact-text"}>{props.contactPerson}</span> + </li> + <li className={"horizontal-list-contact"}> + <span className={"left-contact-text"}>Adresse</span> + <span + className={"right-contact-text"}>{props.address_Street}, {props.address_zip} {props.address_Municipality}</span> + </li> + <li className={"horizontal-list-contact"}> + <span className={"left-contact-text"}>Nummer</span> + <span className={"right-contact-text"}>{props.contactNumber}</span> + </li> + </ul> + </div> + <div className={"card-btns"}> + <Link className={"btn"} to={"/project/" + props.id}>Mer Informasjon</Link> + </div> + </div> + ) +} + +export default CardElement diff --git a/webstillas/src/components/projects/elements/preView.js b/webstillas/src/components/projects/preViewSite.js similarity index 61% rename from webstillas/src/components/projects/elements/preView.js rename to webstillas/src/components/projects/preViewSite.js index 2dc2af39d7c0704c8874d1a9220df2b69f807c90..0ff0e7befe02c5b7b8d00c97c362b9316085fa7c 100644 --- a/webstillas/src/components/projects/elements/preView.js +++ b/webstillas/src/components/projects/preViewSite.js @@ -1,100 +1,37 @@ import React from "react"; -import mapboxgl from "mapbox-gl"; -import "./preView.css" -import Tabs from "../tabView/Tabs" -import ScaffoldingCardProject from "../../scaffolding/elements/scaffoldingCardProject"; -import InfoModal from "./Modal"; +import "../../Assets/Styles/preView.css" +import Tabs from "../../Layout/tabView/Tabs" +import ScaffoldingCardProject from "../../components/projects/scaffoldingCardProject"; +import InfoModal from "./transferScaffoldingModal"; import { + MAP_STYLE_V11, PROJECTS_URL_WITH_ID, - PROJECTS_WITH_SCAFFOLDING_URL, WITH_SCAFFOLDING_URL -} from "../../../modelData/constantsFile"; -import img from "./../../mapPage/mapbox-marker-icon-20px-orange.png" -import {GetDummyData} from "../../../modelData/addData"; -import {useQueryClient} from "react-query"; -import {SpinnerDefault} from "../../Spinner"; +} from "../../Constants/apiURL"; +import img from "../../Assets/Images/marker.png" +import {GetCachingData} from "../../Middleware/addData"; +import {SpinnerDefault} from "../Indicators/Spinner"; import ReactMapboxGl, {Marker} from "react-mapbox-gl"; +import {MapBoxAPIKey} from "../../Config/firebaseConfig"; +import {InternalServerError} from "../Indicators/error"; const Map = ReactMapboxGl({ - accessToken: - "pk.eyJ1IjoiYWxla3NhYWIxIiwiYSI6ImNrbnFjbms1ODBkaWEyb3F3OTZiMWd6M2gifQ.vzOmLzHH3RXFlSsCRrxODQ" + accessToken: MapBoxAPIKey }); - -//mapboxgl.accessToken = 'pk.eyJ1IjoiYWxla3NhYWIxIiwiYSI6ImNrbnFjbms1ODBkaWEyb3F3OTZiMWd6M2gifQ.vzOmLzHH3RXFlSsCRrxODQ'; -/* -//Todo refactor class to function -class PreViewClass extends React.Component { - constructor(props) { - super(props); - this.state = { - data: props.data - } - this.mapContainer = React.createRef(); - } - - async componentDidMount() { - const {data} = this.state - console.log(data) - try { - const map = new mapboxgl.Map({ - container: this.mapContainer.current, - style: MAP_STYLE_V11, - center: [data.longitude, data.latitude], - zoom: 15 - }); - - // Create a DOM element for each marker. - const el = document.createElement('div'); - const width = 50; - const height = 50; - el.className = 'marker'; - el.style.backgroundImage = (img); - el.style.width = `${width}px`; - el.style.height = `${height}px`; - el.style.backgroundSize = '100%'; - - el.addEventListener('click', () => { - window.alert("Project: " + data.projectName) - }); - - // Add markers to the map. - new mapboxgl.Marker(el) - .setLngLat([data.longitude, data.latitude]) - .addTo(map); - - } catch (e) { - console.log(e) - } - - } - - - getProjectID() { - const pathSplit = window.location.href.split("/") - return pathSplit[pathSplit.length - 1] - } - - - render() { - return ( - <div className={"preView-Project-Main"}> - <div ref={this.mapContainer} className="map-container-project"/> - </div> - ) - } -}*/ - - +/** + * Function that will display a map where the project is located + * @param props the project to be displayed + * @returns {JSX.Element} + */ function PreViewFunction(props) { const data = props.data - return ( <div className={"preView-Project-Main"}> <Map - style="mapbox://styles/mapbox/streets-v9" // eslint-disable-line + style= {MAP_STYLE_V11} // eslint-disable-line containerStyle={{ height: "93vh", width: "40vw" @@ -117,13 +54,21 @@ function PreViewFunction(props) { } - +/** + * Function that will get the project id form the url. + * @returns {string} project id + */ function getProjectID() { const pathSplit = window.location.href.split("/") return pathSplit[pathSplit.length - 1] } +/** + * Function that will display all the different types of scaffolding + * @param data scaffolding parts + * @returns {JSX.Element} + */ function scaffoldingComponents(data) { return ( <div className={"grid-container-project-scaffolding"}> @@ -142,6 +87,11 @@ function scaffoldingComponents(data) { } +/** + * Will return cards with project information + * @param project to be displayed + * @returns {JSX.Element} + */ function contactInformation(project) { return ( <div> @@ -170,7 +120,7 @@ function contactInformation(project) { </ul> </div> </section> - <section className={"info-card"}> + <section className={"info-card contact-information"}> <div className={"information-highlights preview-info"}> <h3>Kontakt Informasjon</h3> <ul className={"contact-list"}> @@ -194,36 +144,27 @@ function contactInformation(project) { </ul> </div> </section> - - </div> ) } -export const PreView = () => { - const queryClient = useQueryClient() - - const { - isLoading: projectLoad, - data: project - } = GetDummyData(["project", getProjectID()], PROJECTS_URL_WITH_ID + getProjectID() + WITH_SCAFFOLDING_URL) - let projects - let allProjectsLoading - if (queryClient.getQueryData("allProjects") !== undefined) { - projects = queryClient.getQueryData("allProjects") - } - const {isLoading: allProjects, data} = GetDummyData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) - projects = data - allProjectsLoading = allProjects - +/** + * Function that displays the whole page. + * @returns {JSX.Element} + */ +export const PreViewSite = () => { + //Fetching the data of specific project. + const {isLoading: projectLoad, data, isError } = GetCachingData(["project", getProjectID()], PROJECTS_URL_WITH_ID + getProjectID() + WITH_SCAFFOLDING_URL) - if (allProjectsLoading || projectLoad) { + if (projectLoad) { return <SpinnerDefault/> - + } else if(isError){ + return <InternalServerError /> } else { - return ( + const project = JSON.parse(data.text) + return ( <div className={"preView-Project-Main"}> <div className={"map-preview"}> <PreViewFunction data={project[0]}/> @@ -233,7 +174,7 @@ export const PreView = () => { <div label="Kontakt"> {contactInformation(project)} </div> - <div label="stillas-komponenter"> + <div label="Stillasdeler"> <InfoModal id={getProjectID()}/> {scaffoldingComponents(project[0])} </div> diff --git a/webstillas/src/components/projects/projects.js b/webstillas/src/components/projects/projects.js deleted file mode 100644 index a0d5655325929eb808a6bb5cf1a419edb2b2f159..0000000000000000000000000000000000000000 --- a/webstillas/src/components/projects/projects.js +++ /dev/null @@ -1,169 +0,0 @@ -import React, {useState} from "react"; -import "./projects.css" -import CardElement from './elements/card' -import {Route, Routes} from "react-router-dom"; -import {PROJECTS_WITH_SCAFFOLDING_URL} from "../../modelData/constantsFile"; -import {GetDummyData} from "../../modelData/addData"; -import {SpinnerDefault} from "../Spinner"; - -/** - Class that will create an overview of the projects - */ -export function Project(){ - const [fromSize, setFromSize] = useState(0) - const [toSize, setToSize] = useState(0) - - const [searchName, setSearchName] = useState("") - const [selectedOption, setSelectedOption] = useState("") - - - const [startDate, setStartDate] = useState(null); - const [endDate, setEndDate] = useState(null); - const [focusedInput, setFocusedInput] = useState(null); - const handleDatesChange = ({ startDate, endDate }) => { - setStartDate(startDate); - setEndDate(endDate); - }; - - - - - - - - const {isLoading, data} = GetDummyData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) - if (isLoading) { - return( - <SpinnerDefault /> - ) - } else { - return ( - <div className={"main-project-window"}> - - <div className={"main-sidebar"} > - <div className={"search-filter"}> - <p className = {"input-sorting-text"}>Status</p> - <select className={"form-select options"} onChange={(e) => - setSelectedOption(e.target.value)}> - <option defaultValue="">Velg her</option> - <option value={"Active"}>Aktiv</option> - <option value={"Inactive"}>Inaktiv</option> - <option value={"Upcoming"}>Kommende</option> - </select> - </div> - <div className={"search-filter"}> - <p className = {"input-sorting-text"}>Prosjekt navn: </p> - - <input - className={"form-control"} - type="text" - placeholder={"Søk prosjekt navn"} - value={searchName} - onChange={e => { - setSearchName(e.target.value) - }}/> - </div> - <div className={"search-filter"}> - <p className = {"input-sorting-text"}>Stillsmengde: </p> - <div className={"search-filter size"}> - <input - className={"form-control size-search"} - type="number" - placeholder={"Fra"} - min={0} - onWheel={(e) => e.prototype} - onChange={e => setFromSize(Number(e.target.value))} - /> - <input - className={"form-control size-search"} - type="number" - placeholder={"Til"} - min={0} - onChange={e => { - setToSize(Number(e.target.value)) - }} - /> - </div> - </div> - </div> - <div> - <div className={"projects-display"}> - {data.filter(data => (data.projectName.toLowerCase()).includes(searchName.toLowerCase())) - .filter(data => { - if (fromSize !== 0) { - console.log(fromSize) - return data.size > fromSize - } else { - return true - } - }) - .filter(data => { - console.log(startDate) - if (startDate !== null ) { - return formatDate(data.period.startDate) >= startDate._d - } else { - return true - } - }) - .filter(data => { - if (endDate !== null) { - return formatDate(data.period.endDate) <= endDate - } else { - return true - } - }) - .filter(data => { - if (toSize !== 0) { - return data.size < toSize - } else { - return true - } - }) - .filter(data => { - if (!(selectedOption.length === 0) && !(selectedOption === "Velg her")) { - return data.state === selectedOption - } else { - return true - } - }) - .map((e) => { - return ( - <div key={e.projectID}> - <Routes> - <Route path="/project/:id" element={<CardElement data={e}/>}/> - </Routes> - <CardElement key={e.projectID} - id={e.projectID} - name={e.projectName} - state={e.state} - rentPeriod={e.period.startDate} - size={e.size} - contactPerson={e.customer.name} - contactNumber={e.customer.number} - address_Street={e.address.street} - address_Municipality={e.address.municipality} - address_zip={e.address.zipcode} - /> - </div> - ); - })} - - </div> - </div> - </div> - - ); - } -} - - - -export function formatDate (inputDate) { - const dateArray = inputDate.split('-') - return new Date(dateArray[2] + '-' + dateArray[1] + '-' + dateArray[0]) -} - -export function formatDateToString (inputDate) { - const dateArray = inputDate.split('-') - return (dateArray[2] + '-' + dateArray[1] + '-' + dateArray[0]) -} diff --git a/webstillas/src/components/scaffolding/elements/scaffoldingCardProject.js b/webstillas/src/components/projects/scaffoldingCardProject.js similarity index 80% rename from webstillas/src/components/scaffolding/elements/scaffoldingCardProject.js rename to webstillas/src/components/projects/scaffoldingCardProject.js index 37f3249bdaab3734a258425278a851218d3b6bad..676018e480792f49e393ab72dcb317d47bd06ab3 100644 --- a/webstillas/src/components/scaffolding/elements/scaffoldingCardProject.js +++ b/webstillas/src/components/projects/scaffoldingCardProject.js @@ -1,5 +1,12 @@ import React from "react"; + +/** + * Function that will display number of expected and registered scaffolding parts in the project. + * + * @param props wil return key, type, expected and registered, of that scaffolding + * @returns {JSX.Element} + */ function ScaffoldingProject(props){ return( <div className={"scaffoldingElement"}> @@ -8,7 +15,7 @@ function ScaffoldingProject(props){ <h3>{props.type.toUpperCase()}</h3> </section> <section className={"image"}> - <img className={"img"} src={require(`../images/${props.type.charAt(0).toUpperCase() + props.type.slice(1)}.jpg`)} alt={""}></img> + <img className={"img"} src={require(`../../Assets/Images/scaffoldingImages/${props.type.charAt(0).toUpperCase() + props.type.slice(1)}.jpg`)} alt={""}></img> </section> <section className={"information-highlights-cta"}> <div className={"information-highlights"}> diff --git a/webstillas/src/components/projects/transferScaffoldingModal.js b/webstillas/src/components/projects/transferScaffoldingModal.js new file mode 100644 index 0000000000000000000000000000000000000000..bbd54c2f26a66bf147f4bd2fdc20f304d2b2b5f7 --- /dev/null +++ b/webstillas/src/components/projects/transferScaffoldingModal.js @@ -0,0 +1,253 @@ +import React, {useState} from "react"; +import 'bootstrap/dist/css/bootstrap.min.css'; +import {Button, Modal, Spinner} from 'react-bootstrap'; +import putModel from "../../Middleware/putData"; +import { + PROJECTS_WITH_SCAFFOLDING_URL, + TRANSFER_SCAFFOLDING, +} from "../../Constants/apiURL"; +import {useQueryClient} from "react-query"; +import "../../Assets/Styles/Modal.css" +import {GetCachingData} from "../../Middleware/addData"; + +//https://ordinarycoders.com/blog/article/react-bootstrap-modal + +//JSON body that is used to send request. +const scaffoldingMove = + [ + { + "type": "Bunnskrue", + "quantity": 0 + }, + { + "type": "Diagonalstang", + "quantity": 0 + }, + { + "type": "Enrørsbjelke", + "quantity": 0 + }, + { + "type": "Gelender", + "quantity": 0 + }, + { + "type": "Lengdebjelke", + "quantity": 0 + }, + { + "type": "Plank", + "quantity": 0 + }, + { + "type": "Rekkverksramme", + "quantity": 0 + }, + { + "type": "Spir", + "quantity": 0 + }, + { + "type": "Stillaslem", + "quantity": 0 + }, + { + "type": "Trapp", + "quantity": 0 + } + ] + + +/** + * Function that will endable the user to transfer scaffolding from one location to another. + * + * @param props information of a given project + * @returns {JSX.Element} + */ +export default function InfoModalFunc(props) { + const [show, setShow] = useState(false); + const handleClose = () => setShow(false); + const handleShow = () => setShow(true); + const queryClient = useQueryClient() + + const {isLoading, data: projects} = GetCachingData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) + let project = queryClient.getQueryData(["project", props.id]) + let jsonProject = JSON.parse(project.text) + + const [scaffolding, setScaffolding] = useState(scaffoldingMove); + const [ToProject, setToProject] = useState(""); + const [FromProject, setFromProject] = useState(""); + const [buttonPressed, setButtonPressed] = useState(false) + + /** + * Function to set quantity of scaffolding types + * + * Code taken from https://codesandbox.io/s/react-week-date-view-forked-ruxjr9?file=/src/App.js:857-868 + * + * @param e quantity the user has passed + * @param id of the selected project. + */ + const setQuantity = (e, id) => { + let result = [...scaffolding]; + result = result.map((x) => { + if (x.type.toLowerCase() === id.toLowerCase()) { + const inputvalue = (e.target.value) + x.quantity = parseInt(inputvalue, 10) + return x; + } else return x; + }); + setScaffolding(result) + }; + + + /** + * Function that will execute request to transfer scaffolding parts. + * + * @returns {Promise<void>} + */ + const AddScaffold = async () => { + + JSON.stringify(move) + try { + setButtonPressed(true) + await putModel(TRANSFER_SCAFFOLDING, JSON.stringify(move)) + await queryClient.resetQueries(["project", props.id]) + } catch (e) { + setButtonPressed(false) + if (e.text === "invalid body"){ + window.alert("500 Internal Server Error\nNoe gikk galt! Prøv igjen senere") + }else { + window.alert("Advarsel: Kan ikke overføre antall stillasdeler") + } + } + } + + //JSON body that is sent with request + const move = { + "toProjectID": Number(ToProject), + "fromProjectID": Number(FromProject), + "scaffold": scaffolding + } + + + + //Checks if the user did not set to project equal to from project. + const validFormat = ToProject !== FromProject + let jsonProjects + if (!isLoading){ + jsonProjects = JSON.parse(projects.text) + } + return ( + <> + {isLoading ? <Button className="nextButton" disabled> + <Spinner + as="span" + animation="grow" + size="sm" + role="status" + aria-hidden="true" + /> + Loading + </Button> : + <Button className="nextButton" onClick={handleShow}> + Overfør deler til Prosjekt + </Button>} + + <Modal show={show} + onHide={handleClose} + centered + backdrop="static" + dialogClassName="modal-dialog modal-xl" + > + <Modal.Header closeButton> + <Modal.Title>Stillas Overføring</Modal.Title> + </Modal.Header> + <Modal.Body> + <div className={"scaffoldingElement-modal"}> + <div className={"transfer-options"}> + <span>Overfør til prosjekt:</span> + <select + className={"form-select"} + value={ToProject} + onChange={(e) => setToProject(e.target.value)}> + <option defaultValue="">Choose here</option> + <option value={0}>Storage</option> + {jsonProjects?.map(e => { + return ( + <option key={e.projectID} value={e.projectID}>{e.projectName}</option> + ) + })} + </select> + </div> + <div> + <span>Overfør fra prosjekt:</span> + <select + className={"form-select"} + value={FromProject} + onChange={(e) => setFromProject(e.target.value)}> + <option defaultValue="">Choose here</option> + <option value={0}>Storage</option> + {jsonProjects?.map(e => { + return ( + <option key={e.projectID} value={e.projectID}>{e.projectName}</option> + ) + })} + </select> + </div> + {jsonProject[0].scaffolding.map(e => { + return ( + <div key={e.type} className={"card"}> + <section className={"header"}> + <h3>{e.type.toUpperCase()}</h3> + </section> + <section className={"image"}> + <img className={"img"} + src={require(`../../Assets/Images/scaffoldingImages/${e.type.charAt(0).toUpperCase() + e.type.slice(1)}.jpg`)} + alt={""}></img> + </section> + <input + className={"form-control"} + placeholder={"Enter quantity of scaffolding parts to transfer"} + type="number" + min={0} + key={"input" + e.type} + onChange={(j) => setQuantity(j, e.type)}/> + </div> + ) + } + )} + + </div> + </Modal.Body> + <Modal.Footer> + <Button variant="secondary" onClick={handleClose}> + Close + </Button> + + + {buttonPressed ? <Button disabled> + <Spinner + as="span" + animation="grow" + size="sm" + role="status" + aria-hidden="true" + /> + Transferring... + </Button> : + <Button variant="primary" disabled={!validFormat} onClick={AddScaffold}> + Save Changes + </Button>} + + + </Modal.Footer> + </Modal> + </> + ); + + +} + + + + diff --git a/webstillas/src/components/scaffolding/elements/ModalScaffolding.js b/webstillas/src/components/scaffolding/projectsModal.js similarity index 79% rename from webstillas/src/components/scaffolding/elements/ModalScaffolding.js rename to webstillas/src/components/scaffolding/projectsModal.js index 7a9796b6287d019c48f1e81a986bd0ff33ac5ccd..9d5a2a9e87e41dd2d5a6f75fd3baa00785a0bf83 100644 --- a/webstillas/src/components/scaffolding/elements/ModalScaffolding.js +++ b/webstillas/src/components/scaffolding/projectsModal.js @@ -1,21 +1,26 @@ import React, {useState} from "react"; import {Button, Modal} from "react-bootstrap"; -import img from "../images/spirstillas_solideq_spir_klasse_5_stillas_135_1.jpg"; import {Link} from "react-router-dom"; -import {useQueryClient} from 'react-query' -import {GetDummyData} from "../../../modelData/addData"; -import {PROJECTS_WITH_SCAFFOLDING_URL} from "../../../modelData/constantsFile"; -import "./Modalscaffolding.css" +import {GetCachingData} from "../../Middleware/addData"; +import {PROJECTS_WITH_SCAFFOLDING_URL} from "../../Constants/apiURL"; +import "../../Assets/Styles/Modalscaffolding.css" +import img from "../../Assets/Images/scaffoldingimg.jpg" -function ScaffoldingInProject(type, projects) { - const queryClient = useQueryClient() +/** + * Function will return information about quantity of scaffolding in a specific project, including the end date of the project. + * + * @param type of scaffolding, the user would like more information about + * @returns {JSX.Element|*} + */ +function ScaffoldingInProject(type) { - const {isLoading, data} = GetDummyData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) + const {isLoading, data} = GetCachingData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) if (isLoading){ return <h1>Loading</h1> }else { - const result = data.map((element) => { + const allProjects = JSON.parse(data.text) + const result = allProjects.map((element) => { return { ...element, scaffolding: element.scaffolding.filter((subElement) => subElement.type.toLowerCase() === type.toLowerCase() && subElement.Quantity.expected !== 0) @@ -54,8 +59,6 @@ function ScaffoldingInProject(type, projects) { <Link className={"btn"} to={"/project/" + e.projectID}>Mer infromasjon</Link> </div> </div> - <hr/> - </div> ) } @@ -65,12 +68,17 @@ function ScaffoldingInProject(type, projects) { } +/** + * Function will display a Modal, of with information of the projects that has the selected scaffolding types. + * + * @param props is type of scaffolding. + * @returns {JSX.Element} + */ export default function InfoModal(props) { const [show, setShow] = useState(false); const handleClose = () => setShow(false); const handleShow = () => setShow(true); //https://codesandbox.io/s/react-week-date-view-forked-ruxjr9?file=/src/App.js:857-868 - //todo gjør om variablenavn return ( <div> <Button className="nextButton" onClick={handleShow}> diff --git a/webstillas/src/components/scaffolding/elements/scaffoldingCard.js b/webstillas/src/components/scaffolding/scaffoldingCard.js similarity index 79% rename from webstillas/src/components/scaffolding/elements/scaffoldingCard.js rename to webstillas/src/components/scaffolding/scaffoldingCard.js index 267dc4b2ae0bd7700b1479d66a570656c9c5cf3e..95b06f72b2ff7f87bdf80cc8dcb1774756d4cc0c 100644 --- a/webstillas/src/components/scaffolding/elements/scaffoldingCard.js +++ b/webstillas/src/components/scaffolding/scaffoldingCard.js @@ -1,9 +1,15 @@ import React from 'react' -import './scaffoldingCard.css' -import InfoModal from "./ModalScaffolding"; - +import '../../Assets/Styles/scaffoldingCard.css' +import InfoModal from "./projectsModal"; +/** + * Will return an infromation card, where the user get information about how many of the specific + * scaffolding the storage has, including total amount that is in projects. + * + * @param props type of scaffolding + * @returns {JSX.Element} + */ function CardElement(props){ console.log(props.type) console.log(`../images/${props.type}.jpg`) @@ -14,7 +20,7 @@ function CardElement(props){ <h3>{props.type.toUpperCase()}</h3> </section> <section className={"image"}> - <img className={"img"} src={require(`../images/${props.type.charAt(0).toUpperCase() + props.type.slice(1)}.jpg`)} alt={""}></img> + <img className={"img"} src={require(`../../Assets/Images/scaffoldingImages/${props.type.charAt(0).toUpperCase() + props.type.slice(1)}.jpg`)} alt={""}></img> </section> <section className={"information-highlights-cta"}> <div className={"information-highlights"}> diff --git a/webstillas/src/components/topBar/topBar.css b/webstillas/src/components/topBar/topBar.css deleted file mode 100644 index 958384826df6fd9a9584bac5dda7c697677dc132..0000000000000000000000000000000000000000 --- a/webstillas/src/components/topBar/topBar.css +++ /dev/null @@ -1,19 +0,0 @@ -.toolbar{ - background: #F28A04; - justify-content: space-between; -} - - -.button{ - width: 150px; - height: 70px; -} - -.links{ - margin-inside: 50px; -} - -.appbar{ - position: fixed; -} - diff --git a/webstillas/src/components/topBar/topBar.js b/webstillas/src/components/topBar/topBar.js deleted file mode 100644 index eb61859ea0942748d95b729e73e9d33ac205460a..0000000000000000000000000000000000000000 --- a/webstillas/src/components/topBar/topBar.js +++ /dev/null @@ -1,83 +0,0 @@ -import React from "react"; -import './topBar.css'; -import { - AppBar, Toolbar, Button -} from '@material-ui/core'; -import {Link, NavLink} from "react-router-dom"; -import {DropdownButton, NavDropdown} from "react-bootstrap"; -import DropdownItem from "react-bootstrap/DropdownItem"; -import {useUserAuth} from "../../context/UserAuthContext"; -import {auth} from "../../firebase" -import {orange} from "@material-ui/core/colors"; - -/** - Class that will create a topbar for the application. - */ - -//Todo make list instead of toolbar - //se hva andre nettsider har gjort -const TopBar = () => { - const {logOut} = useUserAuth(); - - - if (!auth.currentUser) { - return ( - <AppBar position="sticky"> - <Toolbar className="toolbar"> - - </Toolbar> - </AppBar> - ) - } else { - return ( - <AppBar position="sticky"> - <Toolbar className="toolbar"> - <Link className="link" to="/prosjekt"> - <Button className="button">Prosjekter</Button> - </Link> - <Link className="link" to="/stillas"> - <Button className="button">Stillasdeler</Button> - </Link> - - <Link className="link" to="/kart"> - <Button className="button">Kart</Button> - </Link> - {/* <Link className="link" to="/logistics"> - <Button className="button">Logistikk</Button> - </Link>*/} - - <NavDropdown id="basic-nav-dropdown1" - title={"Logistikk"} - size="sm" - menuVariant={"dark"} - - > - <DropdownItem> - <Link to={"/addproject/"}>Legg til prosjekt </Link> - </DropdownItem> - <DropdownItem> - <Link to={"/addscaffolding/"}>Legg til stillas</Link> - </DropdownItem> - </NavDropdown> - - - <DropdownButton id="dropdown-button" - title={"Bruker"} - size="sm" - - > - <DropdownItem> - <Link to={"/userinfo/"}>Bruker Informasjon</Link> - </DropdownItem> - - <DropdownItem onClick={logOut}>Logg ut</DropdownItem> - </DropdownButton> - </Toolbar> - </AppBar> - ); - } - - - } - -export default TopBar; diff --git a/webstillas/src/firebase.js b/webstillas/src/firebase.js deleted file mode 100644 index ca330e81dae57f615c6413f244edf0264208861d..0000000000000000000000000000000000000000 --- a/webstillas/src/firebase.js +++ /dev/null @@ -1,16 +0,0 @@ -import { initializeApp } from "firebase/app"; -import { getAuth } from "firebase/auth"; - -const firebaseConfig = { - apiKey: "AIzaSyB5XxJ-AIC_Bm38oOH4TjdeIBA0eNLRl7w", - authDomain: "stillas-16563.firebaseapp.com", - projectId: "stillas-16563", - storageBucket: "stillas-16563.appspot.com", - messagingSenderId: "586975019426", - appId: "1:586975019426:web:83a11475b6ae32ffbc32fb" -}; - -// Initialize Firebase -const app = initializeApp(firebaseConfig); -export const auth = getAuth(app); -export default app; diff --git a/webstillas/src/index.js b/webstillas/src/index.js index cce6a5274f6cd64f644f5213882636e6ae1a2a35..f724eb95f901c0fe3a92f1cf4413a9ba77728da2 100644 --- a/webstillas/src/index.js +++ b/webstillas/src/index.js @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import App from './App'; +import App from './Routes/App'; import {BrowserRouter} from "react-router-dom"; ReactDOM.render( diff --git a/webstillas/src/modelData/addData.js b/webstillas/src/modelData/addData.js deleted file mode 100644 index 68e0bd095035564ef5d3b16205fb907e71e074a5..0000000000000000000000000000000000000000 --- a/webstillas/src/modelData/addData.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import fetchModel from "./fetchData"; -import { useQuery } from 'react-query' - - -export const GetDummyData = (dataName, url) => { - const { isLoading, data} = useQuery(dataName, ()=>{ - return fetchModel(url) - }, { - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false - }) - - return {isLoading, data} -} - -