diff --git a/README.md b/README.md index dad0cce9de0a55102ba4712a4e9c6f7f99886706..f1e61353f43be2a4268ba72430b5d192ba95ff4e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,237 @@ # stillasTracker -# Version 0.1 -# +stillasTracker is a software solution which provides MB-Stillas with an API, a database and two-front end solutions. +The readme will serve as endpoint documentation for the stillastracker API as well as a brief user guide for both of the front end solutions in the repository + +## Endpoint-Documentation: +The following endpoints are available in the current version of the API: + +### Scaffolding (stillasdel) endpoints + +The scaffolding endpoint has GET, POST, PUT and DELETE functionality. Get requests are used +get information regarding scaffolding parts, you can get scaffolding parts based on the queries listed +in the table below. + +**GET requests** + +The GET requests fetches scaffolding parts from the database +``` +/stillastracking/v1/api/unit?type={:type}&id={:id} +/stillastracking/v1/api/unit?type={:type} +/stillastracking/v1/api/unit +``` +**POST and PUT requests** + +The post and put requests are used to add in new scaffolding parts or update them. +``` +/stillastracking/v1/api/unit + +Example body: +{ + "id": "AB23GW", + "type": "Flooring", + "project": "CCHamar", + "batteryLevel": 100 +} +``` + +**DELETE** + +The delete request deletes scaffolding units from the database +``` +/stillastracking/v1/api/unit + +Example body: +[ + { + "id": "4A6352" + }, + { + "id": "56GWAG" + }, + { + "id": "8GTW21" + }, + { + "id": "LPW123" + } +] +``` + + +### Project (byggeprosjekt) endpoints +This endpoint handles all data regarding projects in the database. + +**GET** + +These endpoints are used to fetch information regarding projects from the database. Adding scaffolding=true +after the endpoint will list all the scaffolding parts associated with the project. +``` +/stillastracking/v1/api/project?id={:id}&scaffolding={:scaffolding} +/stillastracking/v1/api/project?name={:name}&scaffolding={:scaffolding} +/stillastracking/v1/api/project?id={:id} +/stillastracking/v1/api/project?name={:name} +/stillastracking/v1/api/project?id={:id} +/stillastracking/v1/api/project?scaffolding={:scaffolding} +/stillastracking/v1/api/project +/stillastracking/v1/api/storage +``` + +**POST and PUT** + +The following endpoint is used to add in new scaffoldingparts + +``` +/stillastracking/v1/api/project + +Example body +{ + "projectID":2321112, + "projectName":"MBStillas", + "latitude":60.79077759591496, + "longitude":10.683249543160402, + "state":"Active", + "size":322, + "period":{ + "startDate":"25-04-2022", + "endDate":"30-04-2022" + }, + "customer":{ + "name":"Martin Ivers", + "number":98435621, + "email":"martin@mail.no" + }, + "address":{ + "street":"Halsetsvea 40", + "zipcode":"2323", + "municipality":"Ingeberg", + "county":"Innlandet" + }, + "geofence":{ + "w-position":{"latitude":-73.98326396942211,"longitude":40.69287790858968}, + "x-position":{"latitude":-73.98387551307742,"longitude":40.6922433936175}, + "y-position":{"latitude":-73.98255586624245,"longitude":40.691999347788055}, + "z-position":{"latitude":-73.98124694824298,"longitude":40.69267453906423} + } + } + +/stillastracking/v1/api/project/scaffold + +Example body: +{ + "toProjectID":755, + "fromProjectID":12, + "scaffold":[{ + "type":"Bunnskrue", + "quantity":1 + },] +} +``` + +**DELETE** + +The endpoint is used to delete projects + +``` +/stillastracking/v1/api/project + +Example body: +[ + { + "id": 430 + }, + { + "id": 420 + } +] +``` + + +### Profile (bruker) endpoints + +The endpoint handles user creation,updates and removals. + +**GET** + +The endpoints below fetches users from the database +``` +/stillastracking/v1/api/user?id={:id} +/stillastracking/v1/api/user?role={:role} +/stillastracking/v1/api/user +``` + +**POST or PUT** + +The endpoint creates or updates users +``` +/stillastracking/v1/api/user + +Example body: +{ + "employeeID": 232, + "name": "Ola Nordmann", + "dateOfBirth": "01.04.1988", + "role": "Storage", + "admin": true +} +``` + +**DELETE** + +The endpoint deletes users from the database +``` +/stillastracking/v1/api/user + +Example body: +[ + {"id" : "12521"}, + {"id" : "12521"}, + {"id" : "12521"}, + {"id" : "12521"}, +] +``` + +### Gateway endpoints +The endpoint handles all management of BLE Gateways in the database + +**GET** + +The following endpoints can be used to fetch gateways from the database +``` +/stillastracking/v1/api/gateway?id={:id} +/stillastracking/v1/api/gateway?projectName={:projectName} +/stillastracking/v1/api/gateway?projectID={:projectID} +/stillastracking/v1/api/gateway +``` + +**POST or PUT** + +The endpoint creates and updates gateways +``` +/stillastracking/v1/api/gateway + +Example body: +{ + "Status": true + "gatewayID": "34AB954B54E4" + "latitude": 59.911491 + "longitude": 10.757933 + "projectID": 4 + "projectName": "CCHamar" +} +``` + +**DELETE** + +The endpoint deletes gateways +``` +/stillastracking/v1/api/gateway + +Example body: +[ + {"id" : "34AB954B54E4"}, + {"id" : "34AB954BABEE4"}, + {"id" : "34TQWD21SDAE4"}, + {"id" : "1241WADQWDQW4"}, +] +``` diff --git a/api/apiTools/basicTools.go b/api/apiTools/basicTools.go index 3e470af5c30ad9b7491bf717560bcbb3b2fba74c..eea24345bba8e035b07ccd3cfa2be68d85583ff5 100644 --- a/api/apiTools/basicTools.go +++ b/api/apiTools/basicTools.go @@ -9,6 +9,12 @@ import ( "strings" ) +/* +Class basicTools contains functions for assisting API endpoints +Last update 19.05.2022 Martin Ivesren +@version 1.0 +*/ + func CreatePath(segments []string) string { var finalPath string for _, s := range segments { diff --git a/api/apiTools/errorHandling.go b/api/apiTools/errorHandling.go index 1e395b26b090e1dfd2543faabe31760efd38f451..57e5e77ec49e5a92ad07b2e66e1e0ca7621b4a3b 100644 --- a/api/apiTools/errorHandling.go +++ b/api/apiTools/errorHandling.go @@ -10,7 +10,7 @@ Code inspired by: Page authored by Matt Silverlock from questionable serviecs Last visit: 08.03.2022 -version 0.1 +version 1.0 Last edited 08.03.2022 by Martin Iversen */ diff --git a/api/cmd/main.go b/api/cmd/main.go index 833f9408718936cbc24cdc7348d1fd7ff5f9f1e2..0f763605a49bb52bc8172ddf245552eba9eb6cd6 100644 --- a/api/cmd/main.go +++ b/api/cmd/main.go @@ -9,7 +9,7 @@ import ( /** Class main Will run the api -Version 0.1 +Version 1.0 Last update 08.03.2022 Martin Iversen */ diff --git a/api/database/databaseSetup.go b/api/database/databaseSetup.go index 65874175edb2ee0ea9e0c6c2e83eb120f4d1ce0a..d3cac49a4223077bcf042871de286cd1e0f86584 100644 --- a/api/database/databaseSetup.go +++ b/api/database/databaseSetup.go @@ -11,6 +11,11 @@ import ( "path/filepath" ) +/* +Class databaseSetup.go created for communicating with database +Last update 19.05.2022 +@version 1.0 +*/ // Ctx Initializing the context to be used with firebase var Ctx context.Context diff --git a/api/endpoints/APIHandler.go b/api/endpoints/APIHandler.go index c4e41adee3895d8aa4c25a500b4b45f2cd9a0da8..8e19854387de4609967f4ce8f6d4c32ac90bf733 100644 --- a/api/endpoints/APIHandler.go +++ b/api/endpoints/APIHandler.go @@ -13,7 +13,7 @@ Class APIHandler.go Class forwards requests to the appropriate endpoint and assigns the port of the program Last modified by martiiv@stud.ntnu.no Date: 06.04.2022 -Version 0.8 +Version 1.0 */ var ( InfoLogger *log.Logger diff --git a/api/endpoints/gateway.go b/api/endpoints/gateway.go index e68964f6d363bb6889415312e3d7fabfa6c6a25c..ad3e6157a2615b55d4601523ac92f23dc05f2e48 100644 --- a/api/endpoints/gateway.go +++ b/api/endpoints/gateway.go @@ -16,6 +16,11 @@ import ( "strconv" ) +/** +Class gateway.go created for managing gateways +@version 1.0 +Last edit 19.05.2022 +*/ var gatewayCollection *firestore.CollectionRef var projectCollection *firestore.DocumentRef diff --git a/api/endpoints/homePage.go b/api/endpoints/homePage.go index b760538483e33c9f8cd33056c61d834efbe6359d..15f53605ef53ac829df786e306bd5a92740d4553 100644 --- a/api/endpoints/homePage.go +++ b/api/endpoints/homePage.go @@ -6,6 +6,12 @@ import ( tool "stillasTracker/api/apiTools" ) +/** +Class homepage.go created but never used, +@version 1.0 +last edit 19.05.2022 +*/ + func homePage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") diff --git a/api/endpoints/profiles.go b/api/endpoints/profiles.go index a83d8daf578fabe4f4d54369236240ac74910708..90d147a35d5ae197b632322c54a551fecd549ef2 100644 --- a/api/endpoints/profiles.go +++ b/api/endpoints/profiles.go @@ -20,7 +20,7 @@ import ( /** Class profiles This class will contain all data formatting and modification regarding the users of the system -Version 0.9 +Version 1.0 Last modified Martin Iversen 07.04.2022 TODO Maybe modularize som functionality the marshall, unmarshall encode routine is repeated often */ diff --git a/api/endpoints/projectTools.go b/api/endpoints/projectTools.go index 143998d59ce08ef2da5a2b85dd79153f7ee9a642..fd806bf26ac3a7099b570ed08be05b59e35d12f6 100644 --- a/api/endpoints/projectTools.go +++ b/api/endpoints/projectTools.go @@ -17,7 +17,7 @@ import ( /* projectTools File contains tools used in the projects.go file Last edited Martin Iversen 07.04.2022 -Version 0.9 +Version 1.0 TODO Delete checkProjectBody? It isn't used */ diff --git a/api/endpoints/projects.go b/api/endpoints/projects.go index 148ac30e6fa91f2a56db5a7b6c96d6b594ea1826..be59a8806a33df68259a4255501362bbe11a97a8 100644 --- a/api/endpoints/projects.go +++ b/api/endpoints/projects.go @@ -22,7 +22,7 @@ import ( Class projects This class will contain all data formatting and handling of the clients projects Class contains the following functions: -Version 0.9 +Version 1.0 Last modified Martin Iversen 07.04.2022 TODO Find alternative for strings.Title since the function is deprecated TODO If possible modularize the unmarshalling and encoding of lists since there is a lot of duplicate code doing this diff --git a/api/endpoints/scaffolding.go b/api/endpoints/scaffolding.go index 14e368cbcd0ab860bc1c5148de63551147162c51..cf8bcce97533a8e7d553c92bae47ecc9dd03e623 100644 --- a/api/endpoints/scaffolding.go +++ b/api/endpoints/scaffolding.go @@ -15,7 +15,7 @@ import ( /** Class scaffolding This class will contain all functions used for the handling of scaffolding units -Version 0.9 +Version 1.0 Last modified Martin Iversen 07.04.2022 TODO make type non case sensitive */ diff --git a/api/endpoints/trackingTools.go b/api/endpoints/trackingTools.go index 8bf36de6e99f6c8739f5a8adeb7a50faccfe9c32..efe31b7587c5eb70d7f3b42b84f9d10ec0056b6f 100644 --- a/api/endpoints/trackingTools.go +++ b/api/endpoints/trackingTools.go @@ -24,8 +24,8 @@ Class gateway The class wil handle all information regarding the cellular gateways in the system The class will contain the following functions: -Version 0.1 -Last modified Martin Iversen +Version 1.0 +Last modified Martin Iversen 19.05.2022 */ func UpdatePosition(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") diff --git a/api/mqtt/mqttBroker.go b/api/mqtt/mqttBroker.go index b9a0e6166a15b9dabd7321b0546bea88aee990d7..226e541ab90da360dcf4dc5a7261d53b89c03212 100644 --- a/api/mqtt/mqttBroker.go +++ b/api/mqtt/mqttBroker.go @@ -10,6 +10,12 @@ import ( "strings" ) +/** +Class mqttBroker.go created for implementing an mqtt broker +This functionality was never implemented fully given time constraints +@version 0.5 +last edited Martin Iversen 19.05.2022 +*/ type AdvPacket struct { msg *igs.Message packet *ibs.Payload diff --git a/api/tests/diagnostics_test.go b/api/tests/diagnostics_test.go deleted file mode 100644 index ca8701d290df4ae2117e23918855f2d7ab28bcb4..0000000000000000000000000000000000000000 --- a/api/tests/diagnostics_test.go +++ /dev/null @@ -1 +0,0 @@ -package tests diff --git a/api/tests/scaffolding_test.go b/api/tests/scaffolding_test.go index 9d99fc7897ccaa9baf706a2ab7b365b324dc6962..c8e236b19068fcd89390a532e464069c97cf86e8 100644 --- a/api/tests/scaffolding_test.go +++ b/api/tests/scaffolding_test.go @@ -1,8 +1,10 @@ package tests import ( + "github.com/gorilla/mux" "github.com/steinfletcher/apitest" "net/http" + "net/http/httptest" "stillasTracker/api/endpoints" "testing" ) @@ -13,12 +15,15 @@ Function for testing the scaffolding endpoint */ func Test_ScaffoldingAPITEST(t *testing.T) { dataBaseTestConnection() - handler := http.HandlerFunc(endpoints.ScaffoldingRequest) + r := mux.NewRouter() //Add list of Scaffoldingparts which sends a post request and creates 12 scaffolding parts t.Run("Add list of Scaffoldingparts", func(t *testing.T) { + r.HandleFunc("/stillastracking/v1/api/unit/", endpoints.ScaffoldingRequest) + ts := httptest.NewServer(r) + defer ts.Close() apitest.New(). - HandlerFunc(handler). + Handler(r). Post("/stillastracking/v1/api/unit/"). Body(`[ { "id": 1, "type": "Spire", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address":null } }, { "id": 2, "type": "Flooring", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } }, { "id": 3, "type": "Short-Flooring", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } }, { "id": 4, "type": "Staircase", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } }, { "id": 5, "type": "Bottom-Screw", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } }, { "id": 6, "type": "Diagonals", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } }, { "id": 7, "type": "Beam1", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } }, { "id": 8, "type": "Beam2", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } }, { "id": 9, "type": "Flooring", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } }, { "id": 10, "type": "Flooring", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } }, { "id": 11, "type": "Spire", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } }, { "id": 12, "type": "Railing", "batteryLevel": 100, "location": { "longitude": null, "latitude": null, "address": null } } ]`). Expect(t). @@ -29,8 +34,11 @@ func Test_ScaffoldingAPITEST(t *testing.T) { //Gets all Scaffolding parts t.Run("Get all Scaffolding parts", func(t *testing.T) { + r.HandleFunc("/stillastracking/v1/api/unit/", endpoints.ScaffoldingRequest) + ts := httptest.NewServer(r) + defer ts.Close() apitest.New(). - HandlerFunc(handler). + HandlerFunc(endpoints.ScaffoldingRequest). Get("/stillastracking/v1/api/unit/"). Expect(t). Body("{\"batteryLevel\":100,\"id\":7,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Beam1\"}\n{\"batteryLevel\":100,\"id\":8,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Beam2\"}\n{\"batteryLevel\":100,\"id\":5,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Bottom-Screw\"}\n{\"batteryLevel\":100,\"id\":6,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Diagonals\"}\n{\"batteryLevel\":100,\"id\":10,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Flooring\"}\n{\"batteryLevel\":100,\"id\":2,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Flooring\"}\n{\"batteryLevel\":100,\"id\":9,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Flooring\"}\n{\"batteryLevel\":100,\"id\":12,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Railing\"}\n{\"batteryLevel\":100,\"id\":3,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Short-Flooring\"}\n{\"batteryLevel\":100,\"id\":1,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Spire\"}\n{\"batteryLevel\":100,\"id\":11,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Spire\"}\n{\"batteryLevel\":100,\"id\":4,\"location\":{\"address\":null,\"latitude\":null,\"longitude\":null},\"type\":\"Staircase\"}\n"). @@ -40,8 +48,11 @@ func Test_ScaffoldingAPITEST(t *testing.T) { //Gets all Scaffolding parts by Flooring t.Run("Get Scaffolding by type", func(t *testing.T) { + r.HandleFunc("/stillastracking/v1/api/unit", endpoints.ScaffoldingRequest).Queries() + ts := httptest.NewServer(r) + defer ts.Close() apitest.New(). - HandlerFunc(handler). + HandlerFunc(endpoints.ScaffoldingRequest). Get("/stillastracking/v1/api/unit"). Query("type", "Flooring"). Expect(t). @@ -50,7 +61,7 @@ func Test_ScaffoldingAPITEST(t *testing.T) { End() apitest.New(). - HandlerFunc(handler). + HandlerFunc(endpoints.ScaffoldingRequest). Get("/stillastracking/v1/api/unit"). Query("type", "Beam1"). Expect(t). @@ -59,7 +70,7 @@ func Test_ScaffoldingAPITEST(t *testing.T) { End() apitest.New(). - HandlerFunc(handler). + HandlerFunc(endpoints.ScaffoldingRequest). Get("/stillastracking/v1/api/unit"). Query("type", "Staircase"). Expect(t). @@ -71,7 +82,7 @@ func Test_ScaffoldingAPITEST(t *testing.T) { //Gets a specific scaffolding part t.Run("Get Individual Scaffolding part", func(t *testing.T) { apitest.New(). - HandlerFunc(handler). + HandlerFunc(endpoints.ScaffoldingRequest). Get("/stillastracking/v1/api/unit"). Query("type", "Flooring"). Query("id", "9"). @@ -83,7 +94,7 @@ func Test_ScaffoldingAPITEST(t *testing.T) { t.Run("Delete all scaffolding parts", func(t *testing.T) { apitest.New(). - HandlerFunc(handler). + HandlerFunc(endpoints.ScaffoldingRequest). Delete("/stillastracking/v1/api/unit/"). Body("[ { \"id\": 7, \"type\": \"Beam1\" }, { \"id\": 8, \"type\": \"Beam2\" }, { \"id\": 5, \"type\": \"Bottom-Screw\" }, { \"id\": 6, \"type\": \"Diagonals\" }, { \"id\": 10, \"type\": \"Flooring\" }, { \"id\": 2, \"type\": \"Flooring\" }, { \"id\": 9, \"type\": \"Flooring\" }, { \"id\": 12, \"type\": \"Railing\" }, { \"id\": 3, \"type\": \"Short-Flooring\" }, { \"id\": 1, \"type\": \"Spire\" }, { \"id\": 11, \"type\": \"Spire\" }, { \"id\": 4, \"type\": \"Staircase\" } ]"). Expect(t). 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/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/components/scaffolding/images/Bunnskrue.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Bunnskrue.jpg similarity index 100% rename from webstillas/src/components/scaffolding/images/Bunnskrue.jpg rename to webstillas/src/Assets/Images/scaffoldingImages/Bunnskrue.jpg diff --git a/webstillas/src/components/scaffolding/images/Diagonalstang.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Diagonalstang.jpg similarity index 100% rename from webstillas/src/components/scaffolding/images/Diagonalstang.jpg rename to webstillas/src/Assets/Images/scaffoldingImages/Diagonalstang.jpg diff --git "a/webstillas/src/components/scaffolding/images/Enr\303\270rsbjelke.jpg" "b/webstillas/src/Assets/Images/scaffoldingImages/Enr\303\270rsbjelke.jpg" similarity index 100% rename from "webstillas/src/components/scaffolding/images/Enr\303\270rsbjelke.jpg" rename to "webstillas/src/Assets/Images/scaffoldingImages/Enr\303\270rsbjelke.jpg" diff --git a/webstillas/src/components/scaffolding/images/Gelender.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Gelender.jpg similarity index 100% rename from webstillas/src/components/scaffolding/images/Gelender.jpg rename to webstillas/src/Assets/Images/scaffoldingImages/Gelender.jpg diff --git a/webstillas/src/components/scaffolding/images/Lengdebjelke.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Lengdebjelke.jpg similarity index 100% rename from webstillas/src/components/scaffolding/images/Lengdebjelke.jpg rename to webstillas/src/Assets/Images/scaffoldingImages/Lengdebjelke.jpg diff --git a/webstillas/src/components/scaffolding/images/Plank.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Plank.jpg similarity index 100% rename from webstillas/src/components/scaffolding/images/Plank.jpg rename to webstillas/src/Assets/Images/scaffoldingImages/Plank.jpg diff --git a/webstillas/src/components/scaffolding/images/Rekkverksramme.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Rekkverksramme.jpg similarity index 100% rename from webstillas/src/components/scaffolding/images/Rekkverksramme.jpg rename to webstillas/src/Assets/Images/scaffoldingImages/Rekkverksramme.jpg diff --git a/webstillas/src/components/scaffolding/images/Spir.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Spir.jpg similarity index 100% rename from webstillas/src/components/scaffolding/images/Spir.jpg rename to webstillas/src/Assets/Images/scaffoldingImages/Spir.jpg diff --git a/webstillas/src/components/scaffolding/images/Stillaslem.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Stillaslem.jpg similarity index 100% rename from webstillas/src/components/scaffolding/images/Stillaslem.jpg rename to webstillas/src/Assets/Images/scaffoldingImages/Stillaslem.jpg diff --git a/webstillas/src/components/scaffolding/images/Trapp.jpg b/webstillas/src/Assets/Images/scaffoldingImages/Trapp.jpg similarity index 100% rename from webstillas/src/components/scaffolding/images/Trapp.jpg rename to webstillas/src/Assets/Images/scaffoldingImages/Trapp.jpg 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/components/Spinner.css b/webstillas/src/Assets/Styles/Spinner.css similarity index 100% rename from webstillas/src/components/Spinner.css rename to webstillas/src/Assets/Styles/Spinner.css 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 100% rename from webstillas/src/components/logistics/scaffold/addScaffolding.css rename to webstillas/src/Assets/Styles/addScaffolding.css 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 100% rename from webstillas/src/components/projects/projects.css rename to webstillas/src/Assets/Styles/projects.css diff --git a/webstillas/src/components/scaffolding/scaffolding.css b/webstillas/src/Assets/Styles/scaffolding.css similarity index 100% rename from webstillas/src/components/scaffolding/scaffolding.css rename to webstillas/src/Assets/Styles/scaffolding.css 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 95% rename from webstillas/src/context/UserAuthContext.js rename to webstillas/src/Config/UserAuthContext.js index fdbc08fa5d5125e9036fa01c20bd5d828fde9ac6..f5dc8fe203ce763167327a21a456f8c509b2247a 100644 --- a/webstillas/src/context/UserAuthContext.js +++ b/webstillas/src/Config/UserAuthContext.js @@ -5,12 +5,11 @@ 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. * @@ -51,11 +50,8 @@ export function UserAuthContextProvider({ children }) { } - /** - * - * - */ - //Todo kommenter + + //todo kommenter useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (currentuser) => { console.log("Auth", currentuser); @@ -66,6 +62,8 @@ export function UserAuthContextProvider({ children }) { }; }, []); + + return ( <userAuthContext.Provider value={{ user, logIn, signUp, logOut }} diff --git a/webstillas/src/firebase.js b/webstillas/src/Config/firebase.js similarity index 100% rename from webstillas/src/firebase.js rename to webstillas/src/Config/firebase.js diff --git a/webstillas/src/modelData/constantsFile.js b/webstillas/src/Constants/apiURL.js similarity index 77% rename from webstillas/src/modelData/constantsFile.js rename to webstillas/src/Constants/apiURL.js index 39706b9c2050a315b7197ea8e31f160a2eedcff0..14d1732e4c275b698324c834c0d4f34128bf4781 100644 --- a/webstillas/src/modelData/constantsFile.js +++ b/webstillas/src/Constants/apiURL.js @@ -1,11 +1,10 @@ -export const PROJECTS_WITH_SCAFFOLDING_URL = "projct?scaffolding=true" +export const PROJECTS_WITH_SCAFFOLDING_URL = "project?scaffolding=true" 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/components/constants.js b/webstillas/src/Constants/webURL.js similarity index 74% rename from webstillas/src/components/constants.js rename to webstillas/src/Constants/webURL.js index 7ba12642b45fc92742cc3548bc985b82ffbc4290..c3aed2aecc5480cc8e7e93b09b5a42ea3e669a84 100644 --- a/webstillas/src/components/constants.js +++ b/webstillas/src/Constants/webURL.js @@ -2,8 +2,8 @@ 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 = "/addproject" -export const ADD_SCAFFOLDING_URL = "/addscaffolding" +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" 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/modelData/addData.js b/webstillas/src/Middleware/addData.js similarity index 59% rename from webstillas/src/modelData/addData.js rename to webstillas/src/Middleware/addData.js index cd7c16716c6b7fdb207da0783594834039e92ca4..1025810cd22ce1e8d5d30603267378be8b53a3a7 100644 --- a/webstillas/src/modelData/addData.js +++ b/webstillas/src/Middleware/addData.js @@ -3,7 +3,15 @@ import fetchModel from "./fetchData"; import { useQuery } from 'react-query' //Todo set timeout -export const GetDummyData = (dataName, url) => { + +/** + * 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) }, { @@ -12,7 +20,6 @@ export const GetDummyData = (dataName, url) => { 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 78% rename from webstillas/src/modelData/postModel.js rename to webstillas/src/Middleware/postModel.js index c0de311a2d8eac8db64c3a17eb68354afac02ef8..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,7 +22,7 @@ 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 }))); 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 74% rename from webstillas/src/components/Login.js rename to webstillas/src/Pages/Login.js index 1ed3ecdc591cc76f19b276f9ccdb778a774e40e4..d773da8897214d9ef0f82dec296c00322fe66465 100644 --- a/webstillas/src/components/Login.js +++ b/webstillas/src/Pages/Login.js @@ -2,9 +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 {PROJECT_URL, SIGNUP} from "./constants"; +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(""); @@ -12,6 +19,11 @@ 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(""); @@ -25,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}> @@ -46,7 +58,7 @@ const Login = () => { /> </Form.Group> - <div className="d-grid gap-2"> + <div className="loginbtn"> <Button variant="primary" type="Submit"> Logg inn </Button> @@ -54,10 +66,10 @@ const Login = () => { </Form> <hr /> </div> - <div className="p-4 box mt-3 text-center"> + <div className="logintxt"> Har du ikke en bruker? <Link to={SIGNUP}>Registrer</Link> </div> - </> + </div> ); }; diff --git a/webstillas/src/components/Signup.js b/webstillas/src/Pages/Signup.js similarity index 59% rename from webstillas/src/components/Signup.js rename to webstillas/src/Pages/Signup.js index 0894b34690d3a5a484afcf5617dacee4435589aa..8ac81235731fcca1a3323c37959352c22ddb0b81 100644 --- a/webstillas/src/components/Signup.js +++ b/webstillas/src/Pages/Signup.js @@ -2,10 +2,19 @@ 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 { formatDateToString} from "./projects/projects"; +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(""); @@ -14,16 +23,18 @@ const Signup = () => { 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(""); + /** + * 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 = @@ -39,23 +50,21 @@ const Signup = () => { "admin": admin, "dateOfBirth": birthDay } - postModel("user", user) - .then(() => navigate("/")) - .catch(e => console.log(e)) - }).catch( - e => console.log(e) - ) + JSON.stringify(user) + postModel(USER_POST_URL, user) + .then(() => navigate(LOGIN)) + }) } catch (err) { + console.log(err) setError(err.message); } }; return ( - <> - <div className="p-4 box"> - <h2 className="mb-3">Firebase Auth Signup</h2> + <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"> @@ -81,24 +90,29 @@ const Signup = () => { /> </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> + <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="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.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> + </Form.Select> + </Form.Group> + </div> - <label htmlFor="startDate">Fødselsdag</label> - <input type="date" onChange={(event) => setBirthDay(formatDateToString(event.target.value))}/> + + <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 @@ -114,17 +128,17 @@ const Signup = () => { onChange={(e) => setPassword(e.target.value)} /> </Form.Group> - <div className="d-grid gap-2"> + <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 className="p-4 box mt-3 text-center"> - Har du allerede en bruker? <Link to="/">Logg inn</Link> - </div> - </> + </div> ); }; diff --git a/webstillas/src/components/logistics/project/map.js b/webstillas/src/Pages/addProjectMap.js similarity index 71% rename from webstillas/src/components/logistics/project/map.js rename to webstillas/src/Pages/addProjectMap.js index 9599fae1091f505974d86310c2ef65a986e25cc2..8eee79ff01e63fe964d8d224df0dcb715392fcee 100644 --- a/webstillas/src/components/logistics/project/map.js +++ b/webstillas/src/Pages/addProjectMap.js @@ -2,15 +2,18 @@ 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 {MAP_STYLE_V11, 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" -import {MapBoxAPIKey} from "../../../firebaseConfig"; -import {AlertCatch} from "../../error/error"; +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 Map = ReactMapboxGl({ +const AddProjectMap = ReactMapboxGl({ accessToken: MapBoxAPIKey }); @@ -18,14 +21,18 @@ const Map = ReactMapboxGl({ * Function that will display a map, and allow a user to draw a polygon. * * @param props variables sent from previous view. - * @returns {JSX.Element} Map with draw controllers. + * @returns {JSX.Element} AddProjectMap with draw controllers. */ export function MapClass(props) { //Query client that will manage the caching. const queryClient = useQueryClient() + let navigate = useNavigate(); + //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. @@ -35,8 +42,7 @@ export function MapClass(props) { */ 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") + window.alert("Format ikke godkjent! Kun fire punkter er tillatt ") } else { setMapInfo(features) setOk(true) @@ -82,11 +88,15 @@ export function MapClass(props) { * @constructor */ const AddProjectRequest = async () => { + try { - console.log(JSON.stringify(project)) - await postModel(PROJECTS_URL, JSON.stringify(project)) - await queryClient.refetchQueries("allProjects") + setButtonPressed(true) + await postModel(PROJECTS_URL, (project)) + await queryClient.refetchQueries("allProjects").then( + navigate(PROJECT_URL) + ) } catch (e) { + setButtonPressed(true) AlertCatch() } } @@ -96,7 +106,7 @@ export function MapClass(props) { return ( <div className="App"> <div className={"map"}> - <Map + <AddProjectMap style={MAP_STYLE_V11} containerStyle={{ height: "80vh", @@ -121,9 +131,24 @@ export function MapClass(props) { <ZoomControl position="bottom-right" /> - </Map> + </AddProjectMap> </div> - <button className={"confirm-btn"} disabled={!ok || !props.valid} onClick={AddProjectRequest}>Add Project</button> + + {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/components/logistics/scaffold/addScaffolding.js b/webstillas/src/Pages/addScaffolding.js similarity index 80% rename from webstillas/src/components/logistics/scaffold/addScaffolding.js rename to webstillas/src/Pages/addScaffolding.js index 4b32aed45c07fa68139253ff2459024178025d28..89a6e945aa37d82607a26912f418516f283b319b 100644 --- a/webstillas/src/components/logistics/scaffold/addScaffolding.js +++ b/webstillas/src/Pages/addScaffolding.js @@ -1,7 +1,7 @@ import React, {useState} from 'react' -import postModel from "../../../modelData/postModel"; -import {SCAFFOLDING_URL} from "../../../modelData/constantsFile"; -import "./addScaffolding.css" +import postModel from "../Middleware/postModel"; +import {SCAFFOLDING_URL} from "../Constants/apiURL"; +import "../Assets/Styles/addScaffolding.css" import {Alert} from "react-bootstrap"; @@ -22,13 +22,10 @@ function AddScaffolding() { address: "" } }) - const [postSucsess, setPostSucsess] = useState(null) + //Verification of a buttonPress const [buttonPress, setButtonPress] = useState(false) - - - /** * Returns card to write id scaffolding type. * @returns {JSX.Element} @@ -49,7 +46,7 @@ function AddScaffolding() { <p className={"input-sorting-text"}>Stillasdel:</p> <select className={"form-select scaffolding-input"} - value={"Test"} + value={scaffolding.type} onChange={(e) => { //setting the type setScaffolding({...scaffolding, type: e.target.value}) @@ -71,37 +68,40 @@ function AddScaffolding() { } + const [type, setType] = useState("") + + + /** + * Function that will add the new scaffolding object + * + * @returns {Promise<void>} + */ const postRequest = async () => { setButtonPress(true) const body = [ scaffolding ] try { - //posting body - const promise = await postModel(SCAFFOLDING_URL, (body)) - setPostSucsess(promise.statusCode) + await postModel(SCAFFOLDING_URL, (body)) + setType("success") + } catch (e) { - console.log(e) + setType("danger") } } - - - - - console.log(postSucsess, buttonPress) return ( <div className={"main-add-scaffolding"}> - {(postSucsess === 201) ? + {(type === "success") ? (<Alert className={"alert-success"} - key={"success"} variant={"success"}> - Stillasdel har blitt registrert - </Alert>): null } - {(postSucsess !== 201 && buttonPress) ? + 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 } + </Alert>) : null} <div className={"info-card"}> {scaffoldingInformation()} <div className={"btn-add-scaffolding"}> 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/components/projects/projects.js b/webstillas/src/Pages/projects.js similarity index 50% rename from webstillas/src/components/projects/projects.js rename to webstillas/src/Pages/projects.js index 40e9d1db41b31e89d675fefd5e4495b342940c48..ef9f8ca68ea8ef093f5d364018196764b225f5eb 100644 --- a/webstillas/src/components/projects/projects.js +++ b/webstillas/src/Pages/projects.js @@ -1,119 +1,109 @@ import React, {useState} from "react"; -import "./projects.css" -import CardElement from './elements/card' +import "../Assets/Styles/projects.css" +import CardElement from '../components/projects/mainProjectCard' import {Route, Routes} from "react-router-dom"; -import {PROJECTS_WITH_SCAFFOLDING_URL} from "../../modelData/constantsFile"; -import {GetDummyData} from "../../modelData/addData"; -import {SpinnerDefault} from "../Spinner"; -import DatePicker from "react-datepicker" -import {InternalServerError} from "../error/error"; +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(){ +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, isError} = GetDummyData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) - + const {isLoading, data, isError} = GetCachingData("allProjects", PROJECTS_WITH_SCAFFOLDING_URL) + //If data is loading if (isLoading) { - return( - <SpinnerDefault /> + return ( + <SpinnerDefault/> ) - } else if (isError){ + } 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> + <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"} - type="date" - onChange={e => { - setStartDate(formatDateToString(e.target.value)) - }}/> - </div> - <div className={"search-filter"}> - <p className = {"input-sorting-text"}>Til dato: </p> + 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"} - type="date" + className={"form-control size-search"} + type="number" + placeholder={"Til"} + min={0} onChange={e => { - setEndDate(formatDateToString(e.target.value)) - }}/> + 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"}> - {data.filter(data => (data.projectName.toLowerCase()).includes(searchName.toLowerCase())) + {allProjects.filter(data => (data.projectName.toLowerCase()).includes(searchName.toLowerCase())) .filter(data => { if (fromSize !== 0) { console.log(fromSize) @@ -124,7 +114,7 @@ export function Project(){ }) .filter(data => { console.log(startDate) - if (startDate !== null ) { + if (startDate !== null) { return formatDate(data.period.startDate) >= formatDate(startDate) } else { return true @@ -182,13 +172,22 @@ export function Project(){ } - -export function formatDate (inputDate) { +/** + * 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]) } -export function formatDateToString (inputDate) { +/** + * 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/App.js b/webstillas/src/Routes/App.js similarity index 68% rename from webstillas/src/App.js rename to webstillas/src/Routes/App.js index b56cf1c58e154768e60b7cbeec3f63d502b850d5..567b8eca210a52a59ed964158a01b15025c2ac3a 100644 --- a/webstillas/src/App.js +++ b/webstillas/src/Routes/App.js @@ -1,34 +1,38 @@ -import './App.css'; +import '../Assets/Styles/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 {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 "./components/ProtectedRoute"; -import Login from "./components/Login"; -import Signup from "./components/Signup"; -import {UserAuthContextProvider} from "./context/UserAuthContext"; -import AddProjectFunc from "./components/logistics/project/addProject"; -import AddScaffolding from "./components/logistics/scaffold/addScaffolding"; -import {UserInfo} from "./components/userinformation/userInfo"; -import {NotFound} from "./components/error/error"; +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, - LOGISTICS_URL, MAP_URL, NOTFOUND, PROJECT_URL, PROJECT_URL_ID, SCAFFOLDING_URL, SIGNUP, USERINFO_URL -} from "./components/constants"; +} from "../Constants/webURL"; const queryClient = new QueryClient() + +/** + * Function that will route the + * @returns {JSX.Element} + * @constructor + */ function App() { return ( //Authorisation of user @@ -40,7 +44,7 @@ function App() { <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> <PreView/></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} 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 57a870499519d291c8e8772437e8f97a057c69f4..9ce4cca4a06b2899351731a65cd43c303581b800 100644 --- a/webstillas/src/components/Spinner.js +++ b/webstillas/src/components/Indicators/Spinner.js @@ -1,7 +1,13 @@ import {Spinner} from "react-bootstrap"; import React from "react"; -import "./Spinner.css" +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/error/error.js b/webstillas/src/components/Indicators/error.js similarity index 73% rename from webstillas/src/components/error/error.js rename to webstillas/src/components/Indicators/error.js index 47568b00833878c02bb42b4f32a1bad58d054ef8..398a55bc83c395552b258674f6709acf20fa45bb 100644 --- a/webstillas/src/components/error/error.js +++ b/webstillas/src/components/Indicators/error.js @@ -10,7 +10,7 @@ export function NotFound() { } /* -Error function that will be displayed if a server error occurs. +Error function that will be displayed if a server Indicators occurs. */ export function InternalServerError() { return ( @@ -22,5 +22,5 @@ export function InternalServerError() { export function AlertCatch(){ - window.alert("Something went wrong. Please try again later") + window.alert("En feil oppstod! Prøv igjen senere.") } diff --git a/webstillas/src/components/logistics/project/addProject.js b/webstillas/src/components/addproject/addProject.js similarity index 77% rename from webstillas/src/components/logistics/project/addProject.js rename to webstillas/src/components/addproject/addProject.js index 4c70678befab54d5b74f27b1671eb9cf39858cd6..4005a4945e2453c2445add7c50f1d88eccb9e789 100644 --- a/webstillas/src/components/logistics/project/addProject.js +++ b/webstillas/src/components/addproject/addProject.js @@ -1,10 +1,10 @@ -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 "../../../firebaseConfig"; +import {MapBoxAPIKey} from "../../Config/firebaseConfig"; /** @@ -29,7 +29,7 @@ export default function AddProjectFunc() { projectName: '', latitude: 0, longitude: 0, - state: "Inactive" + state: "" }) const [size, setSize] = useState({size: 0}) @@ -109,7 +109,7 @@ export default function AddProjectFunc() { /** * Function that will validate the customer input - * If input is not valid, then a predefined error message is set. + * 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. @@ -139,7 +139,7 @@ export default function AddProjectFunc() { /** * Function to validate date format. - * If input is not valid, then a predefined error message is set. + * 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. @@ -161,7 +161,7 @@ export default function AddProjectFunc() { /** * Function to validate projectDetails - * If input is not valid, then a predefined error message is set. + * 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. @@ -212,90 +212,95 @@ export default function AddProjectFunc() { */ 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 is oslo the municipality is not set. - 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 - })) } /** @@ -492,6 +497,24 @@ export default function AddProjectFunc() { </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"> 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/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/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 f775abf22fb0069dbcc74e5f636804e01a05f46e..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"}> @@ -200,28 +150,21 @@ function contactInformation(project) { } -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]}/> @@ -231,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/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 8e384981bf2b86d15d4c48a2e8f48367b75c5a92..0000000000000000000000000000000000000000 --- a/webstillas/src/components/topBar/topBar.css +++ /dev/null @@ -1,22 +0,0 @@ -.toolbar{ - background: #F28A04; - justify-content: space-between; -} - - -.button{ - width: 150px; - height: 70px; -} - -.links{ - margin-inside: 50px; -} - -.appbar{ - position: fixed; -} - -.dropdown-button{ - text-decoration-color: rebeccapurple; -} diff --git a/webstillas/src/components/topBar/topBar.js b/webstillas/src/components/topBar/topBar.js deleted file mode 100644 index c451cbfc3a0357ce50d4d1723a8544d867759158..0000000000000000000000000000000000000000 --- a/webstillas/src/components/topBar/topBar.js +++ /dev/null @@ -1,87 +0,0 @@ -import React from "react"; -import './topBar.css'; -import { - AppBar, Toolbar, Button -} from '@material-ui/core'; -import {Link} 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 {GetDummyData} from "../../modelData/addData"; -import {USER_URL} from "../../modelData/constantsFile"; -import {SpinnerDefault} from "../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"; - -/** - Component that will be used as a top bar for the user to navigate throughout the application. - */ -const TopBar = () => { - const {logOut} = useUserAuth(); - let loading, userData - - /*Checking if the user is authenticated - * If so, fetch userdata - */ - if (auth.currentUser !== null) { - const {isLoading, data} = GetDummyData("user", USER_URL + auth.currentUser.uid) - loading = isLoading - userData = 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 { - //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> - <NavDropdown id="basic-nav-dropdown1" - title={"Logistikk"} - size="sm" - > - <DropdownItem> - <Link to={ADD_PROJECT_URL}>Legg til prosjekt </Link> - </DropdownItem> - <DropdownItem> - <Link to={ADD_SCAFFOLDING_URL}>Legg til stillas</Link> - </DropdownItem> - </NavDropdown> - <DropdownButton id="dropdown-button" - title={"userData.name.firstName"} - size="sm" - > - <DropdownItem> - <Link to={USERINFO_URL}>Bruker Informasjon</Link> - </DropdownItem> - <DropdownItem onClick={logOut}>Logg ut</DropdownItem> - </DropdownButton> - </Toolbar> - </AppBar> - ); - } -} - -export default TopBar; 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(