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 */}
+
-
+
{
>
Heidi v2
+
+ {/* Mobile Menu */}
{
onClose={handleCloseNavMenu}
>
{pages.map((page) => (
-
+
+ {/* 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 */}
+
+
+
+
+ Heidi v2
+
+
+ {/* Mobile Menu */}
+
+
+
+
+
+
+
+ {/* Desktop Menu */}
+
+ {pages.map((page) => (
+
+ ))}
+
+
+ {/* User Settings Menu */}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+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) => (
-
- {/* Status icons and date information */}
-
-
-
- {dewar.ready_date ? `Ready: ${new Date(dewar.ready_date).toLocaleDateString()}` : 'N/A'}
-
-
-
-
-
-
-
-
- {dewar.shipping_date ? `Shipped: ${new Date(dewar.shipping_date).toLocaleDateString()}` : 'N/A'}
-
-
-
-
-
-
-
-
- {dewar.arrival_date ? `Arrived: ${new Date(dewar.arrival_date).toLocaleDateString()}` : 'N/A'}
-
-
+
+ {/* Horizontal Stepper for status */}
+
+ {steps.map((label, index) => (
+
+ {
+ const color = getStepIconColor(dewar); // Use status for color
+ return (
+
+ {index === 0 ? (
+
+ ) : index === 1 ? (
+
+ ) : index === 2 ? (
+
+ ) : (
+
+ )}
+
+ );
+ }}>{label}
+ {/* Display associated date */}
+
+ {index === 0 ? dewar.ready_date :
+ index === 1 ? dewar.shipping_date :
+ index === 2 ? dewar.arrival_date : ''}
+
+
+ ))}
+
{/* Delete button if the dewar is selected */}
{localSelectedDewar?.tracking_number === dewar.tracking_number && (
@@ -290,9 +299,8 @@ const ShipmentDetails: React.FC = ({
height: '40px',
marginLeft: 2,
padding: 0,
- alignSelf: 'center'
+ alignSelf: 'center',
}}
- title="Delete 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