diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..b2250da --- /dev/null +++ b/backend/main.py @@ -0,0 +1,206 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi import HTTPException, status +from pydantic import BaseModel +from typing import List, Optional + +app = FastAPI() + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +class ContactPerson(BaseModel): + firstname: str + lastname: str + phone_number: str + email: str + + +class Address(BaseModel): + street: str + city: str + zipcode: str + country: str + + +class Dewar(BaseModel): + id: str + dewar_name: str + tracking_number: str + number_of_pucks: int + number_of_samples: int + return_address: List[Address] + contact_person: List[ContactPerson] + status: str + ready_date: Optional[str] = None + shipping_date: Optional[str] = None + arrival_date: Optional[str] = None + shippingStatus: str + arrivalStatus: str + qrcode: str + + +class Shipment(BaseModel): + shipment_id: str + shipment_name: str + shipment_date: str + shipment_status: str + contact_person: List[ContactPerson] + proposal_number: Optional[str] = None + return_address: List[Address] + comments: Optional[str] = None + dewars: List[Dewar] + + def get_number_of_dewars(self) -> int: + return len(self.dewars) + + def get_shipment_contact_persons(self) -> str: + return self.contact_person + + class Config: + orm_mode = True + + +# Example data for contacts +contacts = [ + ContactPerson(firstname="Frodo", lastname="Baggins", phone_number="123-456-7890", email="frodo.baggins@lotr.com"), + ContactPerson(firstname="Samwise", lastname="Gamgee", phone_number="987-654-3210", email="samwise.gamgee@lotr.com"), + ContactPerson(firstname="Aragorn", lastname="Elessar", phone_number="123-333-4444", + email="aragorn.elessar@lotr.com"), + ContactPerson(firstname="Legolas", lastname="Greenleaf", phone_number="555-666-7777", + email="legolas.greenleaf@lotr.com"), + ContactPerson(firstname="Gimli", lastname="Son of Gloin", phone_number="888-999-0000", + email="gimli.sonofgloin@lotr.com"), + ContactPerson(firstname="Gandalf", lastname="The Grey", phone_number="222-333-4444", + email="gandalf.thegrey@lotr.com"), + ContactPerson(firstname="Boromir", lastname="Son of Denethor", phone_number="111-222-3333", + email="boromir.sonofdenethor@lotr.com"), + ContactPerson(firstname="Galadriel", lastname="Lady of Lothlórien", phone_number="444-555-6666", + email="galadriel.lothlorien@lotr.com"), + ContactPerson(firstname="Elrond", lastname="Half-elven", phone_number="777-888-9999", + email="elrond.halfelven@lotr.com"), + ContactPerson(firstname="Eowyn", lastname="Shieldmaiden of Rohan", phone_number="000-111-2222", + email="eowyn.rohan@lotr.com") +] + +# Example data for return addresses +return_addresses = [ + Address(street='123 Hobbiton St', city='Shire', zipcode='12345', country='Middle Earth'), + Address(street='456 Rohan Rd', city='Edoras', zipcode='67890', country='Middle Earth') +] + +# Example data for dewars +dewars = [ + Dewar( + id='DEWAR001', + dewar_name='Dewar One', + tracking_number='TRACK123', + number_of_pucks=7, + number_of_samples=70, + return_address=[return_addresses[0]], + contact_person=[contacts[0]], + status='Ready', + ready_date='2023-09-30', + shipping_date='2023-10-01', + arrival_date='2023-10-02', + shippingStatus='Shipped', + arrivalStatus='Arrived', + qrcode='QR123DEWAR001' + ), + Dewar( + id='DEWAR002', + dewar_name='Dewar Two', + tracking_number='TRACK124', + number_of_pucks=3, + number_of_samples=33, + return_address=[return_addresses[1]], + contact_person=[contacts[1]], + status='In Transit', + ready_date='2023-10-01', + shipping_date='2023-10-02', + arrival_date='2023-10-04', + shippingStatus='In Transit', + arrivalStatus='Pending', + qrcode='QR123DEWAR002' + ), + Dewar( + id='DEWAR003', + dewar_name='Dewar Three', + tracking_number='TRACK125', + number_of_pucks=4, + number_of_samples=47, + return_address=[return_addresses[0]], + contact_person=[contacts[2]], + status='Pending', + shippingStatus='Ready for Shipping', + arrivalStatus='Pending', + qrcode='QR123DEWAR003' + ), +] +# Example: Attach a specific Dewar by its id to a shipment +specific_dewar_id = 'DEWAR003' # The ID of the Dewar you want to attach + +# Find the Dewar with the matching id +specific_dewar = next((dewar for dewar in dewars if dewar.id == specific_dewar_id), None) + +# Since shipments need dewars, define them afterward +shipments = [ + Shipment( + shipment_id='SHIPMORDOR', + shipment_date='2024-10-10', + shipment_name='Shipment example test', + shipment_status='Delivered', + contact_person=[contacts[1]], + proposal_number='PROJ001', + return_address=[return_addresses[0]], + comments='Handle with care', + dewars=[specific_dewar] # Taking all dewars as an example + ) +] + + + +@app.get("/contacts", response_model=List[ContactPerson]) +async def get_contacts(): + return contacts + + +@app.get("/shipments", response_model=List[Shipment]) +async def get_shipments(): + return shipments + + +@app.get("/dewars", response_model=List[Dewar]) +async def get_dewars(): + return dewars + + +# Endpoint to get the number of dewars in each shipment +@app.get("/shipment_dewars") +async def get_shipment_dewars(): + return [{"shipment_id": shipment.shipment_id, "number_of_dewars": shipment.get_number_of_dewars()} for shipment in + shipments] + +@app.get("/shipment_contact_persons") +async def get_shipment_contact_persons(): + return [{"shipment_id": shipment.shipment_id, "contact_person": shipment.get_shipment_contact_persons()} for shipment in + shipments] + +# Creation of a new shipment +@app.post("/shipments", response_model=Shipment, status_code=status.HTTP_201_CREATED) +async def create_shipment(shipment: Shipment): + # Check for duplicate shipment_id + if any(s.shipment_id == shipment.shipment_id for s in shipments): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Shipment with this ID already exists." + ) + + shipments.append(shipment) + return shipment diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9128be6..17d80e8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,10 +21,12 @@ "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", "dayjs": "^1.11.13", + "openapi-typescript-codegen": "^0.29.0", "react": "^18.3.1", "react-big-calendar": "^1.15.0", "react-dom": "^18.3.1", "react-qr-code": "^2.0.15", + "react-router-dom": "^6.27.0", "react-scheduler": "^0.1.0" }, "devDependencies": { @@ -71,6 +73,22 @@ "node": ">=6.0.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", + "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, "node_modules/@babel/code-frame": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.9.tgz", @@ -1222,6 +1240,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, "node_modules/@mui/core-downloads-tracker": { "version": "6.1.5", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.5.tgz", @@ -1574,6 +1597,14 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@remix-run/router": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", + "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@restart/hooks": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", @@ -1843,8 +1874,7 @@ "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/parse-json": { "version": "4.0.2", @@ -2187,8 +2217,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/babel-plugin-macros": { "version": "3.1.0", @@ -2286,6 +2315,17 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -2355,6 +2395,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2904,6 +2952,19 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2963,12 +3024,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -3111,7 +3205,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -3164,6 +3257,17 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3293,6 +3397,14 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -3341,6 +3453,11 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -3354,6 +3471,21 @@ "node": ">=0.10.0" } }, + "node_modules/openapi-typescript-codegen": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.29.0.tgz", + "integrity": "sha512-/wC42PkD0LGjDTEULa/XiWQbv4E9NwLjwLjsaJ/62yOsoYhwvmBR31kPttn1DzQ2OlGe5stACcF/EIkZk43M6w==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.4", + "camelcase": "^6.3.0", + "commander": "^12.0.0", + "fs-extra": "^11.2.0", + "handlebars": "^4.7.8" + }, + "bin": { + "openapi": "bin/index.js" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3702,6 +3834,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", + "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "dependencies": { + "@remix-run/router": "1.20.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", + "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "dependencies": { + "@remix-run/router": "1.20.0", + "react-router": "6.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scheduler": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/react-scheduler/-/react-scheduler-0.1.0.tgz", @@ -3796,16 +3958,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/rrule": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz", - "integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4052,6 +4204,18 @@ } } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/uncontrollable": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", @@ -4066,6 +4230,14 @@ "react": ">=15.0.0" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", @@ -4203,6 +4375,11 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4f98bcc..450fba4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,10 +23,12 @@ "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", "dayjs": "^1.11.13", + "openapi-typescript-codegen": "^0.29.0", "react": "^18.3.1", "react-big-calendar": "^1.15.0", "react-dom": "^18.3.1", "react-qr-code": "^2.0.15", + "react-router-dom": "^6.27.0", "react-scheduler": "^0.1.0" }, "devDependencies": { diff --git a/frontend/public/shipmentsdb.json b/frontend/public/shipmentsdb.json index 5c14322..1c0914c 100644 --- a/frontend/public/shipmentsdb.json +++ b/frontend/public/shipmentsdb.json @@ -81,7 +81,7 @@ "shipping_date": "", "arrival_date": "", "shippingStatus": "shipped", - "arrivalStatus": "arrived", + "arrivalStatus": "not arrived", "returned": "", "qrcode": "" }, @@ -102,7 +102,7 @@ "shipping_date": "2024-02-21", "arrival_date": "", "shippingStatus": "shipped", - "arrivalStatus": "arrived", + "arrivalStatus": "not arrived", "returned": "", "qrcode": "https://example.com/qrcode/dewar_004" }, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bc5a39f..d3a9afc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,11 +1,22 @@ import React from 'react'; -import ResponsiveAppBar from './components/ResponsiveAppBar.tsx'; +import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; +import ResponsiveAppBar from './components/ResponsiveAppBar'; +import ShipmentView from './pages/ShipmentView'; +import HomePage from './pages/HomeView'; // Assuming this is a default export +import ResultsView from './pages/ResultsView'; +import PlanningView from './pages/PlanningView'; const App: React.FC = () => { return ( -
+ -
+ + } /> + } /> + } /> + } /> + + ); }; diff --git a/frontend/src/components/Calendar.tsx b/frontend/src/components/Calendar.tsx index fe36d5f..f686045 100644 --- a/frontend/src/components/Calendar.tsx +++ b/frontend/src/components/Calendar.tsx @@ -217,7 +217,7 @@ const Calendar: React.FC = () => { eventContent={eventContent} height={700} headerToolbar={{ - left: 'prev,next today', + left: 'prev,next', center: 'title', right: 'dayGridMonth', }} diff --git a/frontend/src/components/ResponsiveAppBar.tsx b/frontend/src/components/ResponsiveAppBar.tsx index f1219dd..c6480fe 100644 --- a/frontend/src/components/ResponsiveAppBar.tsx +++ b/frontend/src/components/ResponsiveAppBar.tsx @@ -11,36 +11,21 @@ import Container from '@mui/material/Container'; import Avatar from '@mui/material/Avatar'; import Tooltip from '@mui/material/Tooltip'; import { Button } from '@mui/material'; +import { Link } from 'react-router-dom'; // Import Link for navigation import logo from '../assets/icons/psi_01_sn.svg'; import '../App.css'; -import { Shipment, Dewar, ContactPerson, Address, Proposal } from '../types.ts'; // Import types from a single statement -import ShipmentView from './ShipmentView.tsx'; -import PlanningView from './PlanningView.tsx'; -const pages = ['Validator', 'Shipments', 'Samples', 'Planning', 'Experiments', 'Results', 'Docs']; +// Page definitions for navigation +const pages = [ + { name: 'Home', path: '/' }, + { name: 'Shipments', path: '/shipments' }, + { name: 'Planning', path: '/planning' }, + { name: 'Results', path: '/results' } +]; const ResponsiveAppBar: React.FC = () => { const [anchorElNav, setAnchorElNav] = useState(null); const [anchorElUser, setAnchorElUser] = useState(null); - const [selectedPage, setSelectedPage] = useState('Shipments'); - const [newShipment, setNewShipment] = useState({ - shipment_id: '', - shipment_name: '', - shipment_status: '', - number_of_dewars: 0, - shipment_date: '', - return_address: [], // Correctly initialize return_address - contact_person: null, // Use null - dewars: [], - }); - const [isCreatingShipment, setIsCreatingShipment] = useState(false); - const [selectedShipment, setSelectedShipment] = useState(null); - const [selectedDewar, setSelectedDewar] = useState(null); - - // Define missing state variables for contacts, addresses, and proposals - const [contactPersons, setContactPersons] = useState([]); - const [returnAddresses, setReturnAddresses] = useState([]); - const [proposals, setProposals] = useState([]); const handleOpenNavMenu = (event: React.MouseEvent) => { setAnchorElNav(event.currentTarget); @@ -50,11 +35,6 @@ const ResponsiveAppBar: React.FC = () => { setAnchorElNav(null); }; - const handlePageClick = (page: string) => { - setSelectedPage(page); - handleCloseNavMenu(); - }; - const handleOpenUserMenu = (event: React.MouseEvent) => { setAnchorElUser(event.currentTarget); }; @@ -63,36 +43,15 @@ const ResponsiveAppBar: React.FC = () => { setAnchorElUser(null); }; - // Updated selectShipment to accept Shipment | null - const selectShipment = (shipment: Shipment | null) => { - setSelectedShipment(shipment); - setIsCreatingShipment(false); - setSelectedDewar(null); - }; - - const handleSaveShipment = () => { - console.log('Saving shipment:', newShipment); - setIsCreatingShipment(false); - setNewShipment({ - shipment_id: '', - shipment_name: '', - shipment_status: '', - number_of_dewars: 0, - shipment_date: '', - return_address: [], // Add return_address to the reset state - contact_person: null, // Use null - dewars: [], - }); - }; - return (
- + {/* Logo and Title */} + PSI logo - + { > Heidi v2 + + {/* Mobile Menu */} { onClose={handleCloseNavMenu} > {pages.map((page) => ( - handlePageClick(page)}> - {page} + + + {page.name} + ))} + + {/* Desktop Menu */} {pages.map((page) => ( ))} + + {/* User Settings Menu */} @@ -180,25 +148,6 @@ const ResponsiveAppBar: React.FC = () => { - - {selectedPage === 'Shipments' && ( - - )} - {selectedPage === 'Planning' && } -
); }; diff --git a/frontend/src/components/ShipmentPanel.tsx b/frontend/src/components/ShipmentPanel.tsx index 541cc1d..cc5448e 100644 --- a/frontend/src/components/ShipmentPanel.tsx +++ b/frontend/src/components/ShipmentPanel.tsx @@ -5,12 +5,13 @@ import AddIcon from '@mui/icons-material/Add'; import DeleteIcon from '@mui/icons-material/Delete'; // Import delete icon import UploadFileIcon from '@mui/icons-material/UploadFile'; // Import the upload icon import UploadDialog from './UploadDialog.tsx'; // Import the UploadDialog component -import { Shipment } from '../types.ts'; // Ensure Shipment type is correctly imported +import {Shipment} from '../types.ts'; // Ensure Shipment type is correctly imported import { SxProps } from '@mui/material'; import bottleGrey from '../assets/icons/bottle-svgrepo-com-grey.svg'; import bottleYellow from '../assets/icons/bottle-svgrepo-com-yellow.svg'; import bottleGreen from '../assets/icons/bottle-svgrepo-com-green.svg'; import bottleRed from '../assets/icons/bottle-svgrepo-com-red.svg'; +import {Shipment_Input, DefaultService, OpenAPI} from "../../openapi"; interface ShipmentPanelProps { selectedPage: string; @@ -22,17 +23,16 @@ interface ShipmentPanelProps { } const ShipmentPanel: React.FC = ({ - setIsCreatingShipment, - newShipment, - setNewShipment, - selectedPage, + //setIsCreatingShipment, + //newShipment, + //setNewShipment, + //selectedPage, selectShipment, sx, }) => { const [shipments, setShipments] = useState([]); const [selectedShipment, setSelectedShipment] = useState(null); const [error, setError] = useState(null); - const [uploadDialogOpen, setUploadDialogOpen] = useState(false); const handleOpenUploadDialog = () => { @@ -43,6 +43,7 @@ const ShipmentPanel: React.FC = ({ setUploadDialogOpen(false); }; + // Status icon mapping const statusIconMap: Record = { "In Transit": bottleYellow, @@ -52,9 +53,13 @@ const ShipmentPanel: React.FC = ({ }; useEffect(() => { + OpenAPI.BASE='http://127.0.0.1:8000' const fetchShipments = async () => { + console.log('trying to fetch some shipments'); try { - const response = await fetch('/shipmentsdb.json'); + DefaultService.getShipmentsShipmentsGet().then((s : Shipment_Input[]) => { + setShipments(s); + }); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const data = await response.json(); @@ -95,21 +100,21 @@ const ShipmentPanel: React.FC = ({ variant="contained" color="primary" startIcon={} - onClick={() => { - setNewShipment({ - shipment_id: '', // Ensure this matches the Shipment type - shipment_name: '', - shipment_status: '', - number_of_dewars: 0, - shipment_date: '', - contact_person: null, // Keep this as null to match Shipment type - dewars: [], - return_address: [], // Make sure return_address is initialized as an array - proposal_number: undefined, // Optional property - comments: '', // Optional property - }); - setIsCreatingShipment(true); - }} + //onClick={() => { + // setNewShipment({ + // shipment_id: '', // Ensure this matches the Shipment type + // shipment_name: '', + // shipment_status: '', + // number_of_dewars: 0, + // shipment_date: '', + // contact_person: null, // Keep this as null to match Shipment type + // dewars: [], + // return_address: [], // Make sure return_address is initialized as an array + // proposal_number: undefined, // Optional property + // comments: '', // Optional property + // }); + // setIsCreatingShipment(true); + //}} sx={{ marginBottom: 2, padding: '10px 16px' }} > Create Shipment diff --git a/frontend/src/components/UploadDialog.tsx b/frontend/src/components/UploadDialog.tsx index 884cb81..c0921f6 100644 --- a/frontend/src/components/UploadDialog.tsx +++ b/frontend/src/components/UploadDialog.tsx @@ -130,6 +130,9 @@ const UploadDialog: React.FC = ({ open, onClose }) => { )} {fileSummary && ( + + File uploaded successfully! + File Summary: diff --git a/frontend/src/components/DewarDetails.tsx b/frontend/src/keep/DewarDetails.tsx similarity index 100% rename from frontend/src/components/DewarDetails.tsx rename to frontend/src/keep/DewarDetails.tsx diff --git a/frontend/src/components/ParentComponent.tsx b/frontend/src/keep/ParentComponent.tsx similarity index 85% rename from frontend/src/components/ParentComponent.tsx rename to frontend/src/keep/ParentComponent.tsx index b37b0f2..8a962d4 100644 --- a/frontend/src/components/ParentComponent.tsx +++ b/frontend/src/keep/ParentComponent.tsx @@ -1,18 +1,25 @@ import React, { useEffect, useState } from 'react'; import DewarDetails from './DewarDetails.tsx'; import { Shipment, ContactPerson, Address, Dewar } from '../types.ts'; -import shipmentData from '../../public/shipmentsdb.json'; // Adjust the path to where your JSON file is stored +import shipmentData from '../../public/shipmentsdb.json'; +import {Contact, DefaultService, OpenAPI} from "../../openapi"; const ParentComponent = () => { const [dewars, setDewars] = useState([]); - const [contactPersons, setContactPersons] = useState([]); + const [contactPersons, setContactPersons] = useState([]); const [returnAddresses, setReturnAddresses] = useState([]); const [selectedShipment, setSelectedShipment] = useState(null); useEffect(() => { + OpenAPI.BASE='http://127.0.0.1:8000' + const firstShipment = shipmentData.shipments[0] as Shipment; // Ensure proper typing setSelectedShipment(firstShipment); - setContactPersons(firstShipment.contact_person || []); // Set to array directly + console.log('this is meeee'); + DefaultService.getContactsContactsGet().then((c : Contact[]) => { + setContactPersons(c); + }); + if (firstShipment.return_address) { setReturnAddresses(firstShipment.return_address); // Set to array directly } diff --git a/frontend/src/keep/ResponsiveAppBar.tsx b/frontend/src/keep/ResponsiveAppBar.tsx new file mode 100644 index 0000000..3ae6920 --- /dev/null +++ b/frontend/src/keep/ResponsiveAppBar.tsx @@ -0,0 +1,157 @@ +import React, { useState } from 'react'; +import AppBar from '@mui/material/AppBar'; +import Box from '@mui/material/Box'; +import Toolbar from '@mui/material/Toolbar'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import MenuIcon from '@mui/icons-material/Menu'; +import Container from '@mui/material/Container'; +import Avatar from '@mui/material/Avatar'; +import Tooltip from '@mui/material/Tooltip'; +import { Button } from '@mui/material'; +import logo from '../assets/icons/psi_01_sn.svg'; +import '../App.css'; + +// Page definitions for navigation +const pages = [ + { name: 'Home', path: '/' }, + { name: 'Shipments', path: '/shipments' }, + { name: 'Planning', path: '/planning' }, + { name: 'Results', path: '/results' } +]; + +const ResponsiveAppBar: React.FC = () => { + const [anchorElNav, setAnchorElNav] = useState(null); + const [anchorElUser, setAnchorElUser] = useState(null); + const [selectedPage, setSelectedPage] = useState('Home'); // Default to 'Home' page + + const handleOpenNavMenu = (event: React.MouseEvent) => { + setAnchorElNav(event.currentTarget); + }; + + const handleCloseNavMenu = () => { + setAnchorElNav(null); + }; + + const handlePageClick = (page: string) => { + setSelectedPage(page); + handleCloseNavMenu(); + }; + + const handleOpenUserMenu = (event: React.MouseEvent) => { + setAnchorElUser(event.currentTarget); + }; + + const handleCloseUserMenu = () => { + setAnchorElUser(null); + }; + + return ( +
+ + + + {/* Logo and Title */} + + PSI logo + + + Heidi v2 + + + {/* Mobile Menu */} + + + + + + {pages.map((page) => ( + handlePageClick(page.name)}> + {page.name} + + ))} + + + + {/* Desktop Menu */} + + {pages.map((page) => ( + + ))} + + + {/* User Settings Menu */} + + + + + + + + DUO + Logout + + + + + +
+ ); +}; + +export default ResponsiveAppBar; diff --git a/frontend/src/components/ShipmentDetails.tsx b/frontend/src/keep/ShipmentDetails.tsx similarity index 70% rename from frontend/src/components/ShipmentDetails.tsx rename to frontend/src/keep/ShipmentDetails.tsx index 7f9577f..a7da54d 100644 --- a/frontend/src/components/ShipmentDetails.tsx +++ b/frontend/src/keep/ShipmentDetails.tsx @@ -1,17 +1,24 @@ import React from 'react'; -import { Box, Typography, Button, Stack, TextField } from '@mui/material'; +import { + Box, + Typography, + Button, + Stack, + TextField, + Stepper, + Step, + StepLabel, +} from '@mui/material'; import ShipmentForm from './ShipmentForm.tsx'; import DewarDetails from './DewarDetails.tsx'; import { Shipment, Dewar, ContactPerson, Proposal, Address } from '../types.ts'; import { SxProps } from '@mui/system'; import QRCode from 'react-qr-code'; -import bottleGrey from '../assets/icons/bottle-svgrepo-com-grey.svg'; -import bottleYellow from '../assets/icons/bottle-svgrepo-com-yellow.svg'; -import bottleGreen from '../assets/icons/bottle-svgrepo-com-green.svg'; -import ArrowForwardIcon from "@mui/icons-material/ArrowForward"; +import bottleIcon from '../assets/icons/bottle-svgrepo-com-grey.svg'; import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive"; import StoreIcon from "@mui/icons-material/Store"; -import DeleteIcon from "@mui/icons-material/Delete"; // Import delete icon +import DeleteIcon from "@mui/icons-material/Delete"; +import {Contact} from "../../openapi"; // Import delete icon interface ShipmentDetailsProps { selectedShipment: Shipment | null; @@ -20,7 +27,7 @@ interface ShipmentDetailsProps { newShipment: Shipment; setNewShipment: React.Dispatch>; handleSaveShipment: () => void; - contactPersons: ContactPerson[]; + contactPersons: Contact[]; proposals: Proposal[]; returnAddresses: Address[]; sx?: SxProps; @@ -46,16 +53,8 @@ const ShipmentDetails: React.FC = ({ tracking_number: '', }); - const shippingStatusMap: { [key: string]: string } = { - "not shipped": "grey", - "shipped": "yellow", - "arrived": "green", - }; - - const arrivalStatusMap: { [key: string]: string } = { - "not arrived": "grey", - "arrived": "green", - }; + // Step titles based on your status + const steps = ['Ready for Shipping', 'Shipped', 'Arrived']; React.useEffect(() => { if (localSelectedDewar) { @@ -143,6 +142,17 @@ const ShipmentDetails: React.FC = ({ console.log('Add new return address:', address); }; + // Function to determine the color of the step icon + const getStepIconColor = (dewar: Dewar) => { + const { status, shippingStatus, arrivalStatus } = dewar; + if (status === 'Ready for Shipping') return 'green'; // Bottle Icon + if (shippingStatus === 'shipped') return 'green'; // Plane Icon + if (shippingStatus === 'not shipped') return 'yellow'; // Plane Icon + if (arrivalStatus === 'arrived') return 'green'; // Store Icon + if (arrivalStatus === 'not arrived') return 'yellow'; // Store Icon + return 'grey'; // Default color + }; + return ( {/* Add Dewar Button - only visible if no dewar is selected */} @@ -177,7 +187,6 @@ const ShipmentDetails: React.FC = ({ )} - {selectedShipment.shipment_name} Number of Pucks: {totalPucks} Number of Samples: {totalSamples} @@ -195,28 +204,26 @@ const ShipmentDetails: React.FC = ({ returnAddresses={returnAddresses} // Pass return addresses addNewContactPerson={addNewContactPerson} // Pass function to add a new contact person addNewReturnAddress={addNewReturnAddress} // Pass function to add a new return address - shipping_date={localSelectedDewar?.shipping_date} // Ensure these are passed - arrival_date={localSelectedDewar?.arrival_date} /> )} - {selectedShipment.dewars.map((dewar: Dewar) => ( + + {selectedShipment.dewars.map((dewar) => ( diff --git a/frontend/src/components/ShipmentForm.tsx b/frontend/src/keep/ShipmentForm.tsx similarity index 96% rename from frontend/src/components/ShipmentForm.tsx rename to frontend/src/keep/ShipmentForm.tsx index b32c9fc..8970a0d 100644 --- a/frontend/src/components/ShipmentForm.tsx +++ b/frontend/src/keep/ShipmentForm.tsx @@ -3,6 +3,8 @@ import { Box, Button, TextField, Typography, Select, MenuItem, Stack, FormContro import { SelectChangeEvent } from '@mui/material'; import { Shipment, ContactPerson, Proposal, Address } from '../types.ts'; // Adjust import paths as necessary import { SxProps } from '@mui/material'; +import {useEffect} from "react"; +import {DefaultService} from "../../openapi"; interface ShipmentFormProps { newShipment: Shipment; @@ -33,21 +35,21 @@ const ShipmentForm: React.FC = ({ }); const [newReturnAddress, setNewReturnAddress] = React.useState(''); - const handleContactPersonChange = (event: SelectChangeEvent) => { + const handleContactPersonChange = (event: SelectChangeEvent) => { const value = event.target.value; if (value === 'new') { setIsCreatingContactPerson(true); setNewShipment({ ...newShipment, contact_person: [] }); // Set to empty array for new person } else { setIsCreatingContactPerson(false); - const selectedPerson = contactPersons.find((person) => person.name === value) || null; + const selectedPerson = contactPersons.find((person) => person.lastname === value) || null; if (selectedPerson) { setNewShipment({ ...newShipment, contact_person: [selectedPerson] }); // Wrap in array } } }; - const handleReturnAddressChange = (event: SelectChangeEvent) => { + const handleReturnAddressChange = (event: SelectChangeEvent) => { const value = event.target.value; if (value === 'new') { setIsCreatingReturnAddress(true); @@ -105,7 +107,7 @@ const ShipmentForm: React.FC = ({ Contact Person