diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..d778b59 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,17 @@ +services: + postgres: + image: postgres:12 + restart: always + user: root + environment: + POSTGRES_USER: xasadmin + POSTGRES_PASSWORD: password + POSTGRES_DB: xasstandarddb + ports: + - '5432:5432' + volumes: + - postgres-data:/var/lib/postgresql/data + - ./database/tables.sql:/docker-entrypoint-initdb.d/create_tables.sql + +volumes: + postgres-data: diff --git a/database/tables.sql b/database/tables.sql index b3d4360..a65ace6 100644 --- a/database/tables.sql +++ b/database/tables.sql @@ -182,7 +182,7 @@ CREATE TABLE beamline ( xray_source TEXT, facility_id INTEGER, PRIMARY KEY (id), - UNIQUE (name), + UNIQUE (facility_id, name), FOREIGN KEY(facility_id) REFERENCES facility (id) ); @@ -203,22 +203,22 @@ INSERT INTO "beamline" VALUES(12,'I18','The Microfocus Beamline','I18 Undulator' INSERT INTO "beamline" VALUES(13,'I20-scanning','Versatile X-ray Spectroscopy','I20 Wiggler', 8); CREATE TABLE person ( - id SERIAL, + id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, identifier TEXT NOT NULL, - PRIMARY KEY (id), + admin BOOLEAN NOT NULL DEFAULT FALSE, UNIQUE (identifier) ); COMMENT ON TABLE person IS 'Table to store unique identifier of user'; CREATE TABLE xas_standard_data ( - id SERIAL, + id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, original_filename TEXT NOT NULL, transmission BOOLEAN NOT NULL, fluorescence BOOLEAN NOT NULL, + emission BOOLEAN NOT NULL, reference BOOLEAN NOT NULL, - location TEXT NOT NULL, - PRIMARY KEY (id) + location TEXT NOT NULL ); COMMENT ON TABLE xas_standard_data IS 'Data file storing the standard data'; @@ -227,7 +227,7 @@ CREATE TYPE review_status_enum AS ENUM('pending', 'approved', 'rejected'); CREATE TYPE licence_enum AS ENUM('cc_by', 'cc_0', 'logged_in_only'); CREATE Table xas_standard ( - id SERIAL, + id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, submitter_id INTEGER NOT NULL, reviewer_id INTEGER, submission_date TIMESTAMP, @@ -240,12 +240,13 @@ CREATE Table xas_standard ( edge_id INTEGER, sample_name TEXT, sample_prep TEXT, + sample_comp TEXT, beamline_id INTEGER, mono_name TEXT, mono_dspacing TEXT, additional_metadata TEXT, + citation TEXT, licence licence_enum NOT NULL, - PRIMARY KEY (id), FOREIGN KEY(submitter_id) REFERENCES person (id), FOREIGN KEY(reviewer_id) REFERENCES person (id), FOREIGN KEY(data_id) REFERENCES xas_standard_data (id), @@ -257,11 +258,10 @@ CREATE Table xas_standard ( COMMENT ON TABLE xas_standard IS 'Metadata relating to an xas standard measurement'; CREATE Table xas_standard_attachment ( - id SERIAL, + id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, xas_standard_id INTEGER NOT NULL, location TEXT NOT NULL, description TEXT, - PRIMARY KEY (id), FOREIGN KEY(xas_standard_id) REFERENCES xas_standard (id) ); diff --git a/xas-standards-api/src/xas_standards_api/app.py b/xas-standards-api/src/xas_standards_api/app.py index f4ea506..41fdc0e 100644 --- a/xas-standards-api/src/xas_standards_api/app.py +++ b/xas-standards-api/src/xas_standards_api/app.py @@ -1,6 +1,6 @@ import datetime import os -from typing import Annotated, List, Optional, Union +from typing import Annotated, List, Optional import requests from fastapi import ( @@ -33,15 +33,17 @@ update_review, ) from .schemas import ( + AdminXASStandardResponse, Beamline, BeamlineResponse, Edge, Element, LicenceType, MetadataResponse, - Review, + Person, + ReviewStatus, XASStandard, - XASStandardAdminResponse, + XASStandardAdminReviewInput, XASStandardInput, XASStandardResponse, ) @@ -121,8 +123,16 @@ async def get_current_user( @app.get("/api/user") -async def check(user_id: str = Depends(get_current_user)): - return {"user": user_id} +async def check( + session: Session = Depends(get_session), user_id: str = Depends(get_current_user) +): + + statement = select(Person).where(Person.identifier == user_id) + person = session.exec(statement).first() + + admin = person is not None and person.admin + + return {"user": user_id, "admin": admin} @app.get("/api/metadata") @@ -157,31 +167,44 @@ def read_edges(session: Session = Depends(get_session)) -> List[Edge]: def read_standards( session: Session = Depends(get_session), element: str | None = None, - admin: bool = False, - response_model=Union[XASStandardResponse, XASStandardAdminResponse], -) -> CursorPage[XASStandardAdminResponse | XASStandardResponse]: +) -> CursorPage[XASStandardResponse]: - statement = select(XASStandard) + statement = select(XASStandard).where( + XASStandard.review_status == ReviewStatus.approved + ) if element: statement = statement.join(Element, XASStandard.element_z == Element.z).where( Element.symbol == element ) - if admin: + return paginate( + session, + statement.order_by(XASStandard.id), + ) - def transformer(x): - return [XASStandardAdminResponse.model_validate(i) for i in x] - else: +@app.get("/api/admin/standards") +def read_standards_admin( + session: Session = Depends(get_session), + user_id: str = Depends(get_current_user), +) -> CursorPage[AdminXASStandardResponse]: - def transformer(x): - return [XASStandardResponse.model_validate(i) for i in x] + statement = select(Person).where(Person.identifier == user_id) + person = session.exec(statement).first() - return paginate( - session, statement.order_by(XASStandard.id), transformer=transformer + if person is None or not person.admin: + raise HTTPException(status_code=401, detail=f"No standard with id={user_id}") + + if not person.admin: + raise HTTPException(status_code=401, detail=f"User {user_id} not admin") + + statement = select(XASStandard).where( + XASStandard.review_status == ReviewStatus.pending ) + return paginate(session, statement.order_by(XASStandard.id)) + @app.get("/api/standards/{id}") async def read_standard( @@ -234,8 +257,21 @@ def add_standard_file( @app.patch("/api/standards") -def submit_review(review: Review, session: Session = Depends(get_session)): - return update_review(session, review) +def submit_review( + review: XASStandardAdminReviewInput, + session: Session = Depends(get_session), + user_id: str = Depends(get_current_user), +): + + statement = select(Person).where(Person.identifier == user_id) + person = session.exec(statement).first() + + if person is None or not person.admin: + raise HTTPException(status_code=401, detail=f"No standard with id={user_id}") + + if not person.admin: + raise HTTPException(status_code=401, detail=f"User {user_id} not admin") + return update_review(session, review, person.id) @app.get("/api/data/{id}") diff --git a/xas-standards-api/src/xas_standards_api/crud.py b/xas-standards-api/src/xas_standards_api/crud.py index 0ac5d2d..233ab77 100644 --- a/xas-standards-api/src/xas_standards_api/crud.py +++ b/xas-standards-api/src/xas_standards_api/crud.py @@ -51,9 +51,11 @@ def get_standard(session, id) -> XASStandard: raise HTTPException(status_code=404, detail=f"No standard with id={id}") -def update_review(session, review): - standard = session.get(XASStandard, review.id) +def update_review(session, review, reviewer_id): + standard = session.get(XASStandard, review.standard_id) standard.review_status = review.review_status + standard.reviewer_comments = review.reviewer_comments + standard.reviewer_id = reviewer_id session.add(standard) session.commit() session.refresh(standard) @@ -89,6 +91,7 @@ def add_new_standard(session, file1, xs_input: XASStandardInput, additional_file fluorescence = "mufluor" in set_labels transmission = "mutrans" in set_labels + reference = "murefer" in set_labels emission = "mutey" in set_labels xsd = XASStandardDataInput( @@ -97,6 +100,7 @@ def add_new_standard(session, file1, xs_input: XASStandardInput, additional_file original_filename=file1.filename, emission=emission, transmission=transmission, + reference=reference, ) new_standard = XASStandard.model_validate(xs_input) diff --git a/xas-standards-api/src/xas_standards_api/schemas.py b/xas-standards-api/src/xas_standards_api/schemas.py index 0091bd2..d86d625 100644 --- a/xas-standards-api/src/xas_standards_api/schemas.py +++ b/xas-standards-api/src/xas_standards_api/schemas.py @@ -6,11 +6,6 @@ from sqlmodel import Column, Enum, Field, Relationship, SQLModel -class Review(BaseModel): - id: int - review_status: str - - class Mono(BaseModel): name: Optional[str] = None d_spacing: Optional[str] = None @@ -27,6 +22,7 @@ class PersonInput(SQLModel): class Person(PersonInput, table=True): id: int | None = Field(primary_key=True, default=None) + admin: bool = False class ElementInput(SQLModel): @@ -107,6 +103,7 @@ class XASStandardDataInput(SQLModel): original_filename: str transmission: bool fluorescence: bool + reference: bool emission: bool location: str @@ -153,7 +150,7 @@ class XASStandard(XASStandardInput, table=True): data_id: int | None = Field(foreign_key="xas_standard_data.id", default=None) reviewer_id: Optional[int] = Field(foreign_key="person.id", default=None) reviewer_comments: Optional[str] = None - review_status: Optional[ReviewStatus] = Field( + review_status: ReviewStatus = Field( sa_column=Column(Enum(ReviewStatus)), default=ReviewStatus.pending ) @@ -161,6 +158,12 @@ class XASStandard(XASStandardInput, table=True): element: Element = Relationship(sa_relationship_kwargs={"lazy": "joined"}) edge: Edge = Relationship(sa_relationship_kwargs={"lazy": "joined"}) beamline: Beamline = Relationship(sa_relationship_kwargs={"lazy": "selectin"}) + submitter: Person = Relationship( + sa_relationship_kwargs={ + "lazy": "selectin", + "foreign_keys": "[XASStandard.submitter_id]", + } + ) class XASStandardResponse(XASStandardInput): @@ -171,7 +174,12 @@ class XASStandardResponse(XASStandardInput): submitter_id: int -class XASStandardAdminResponse(XASStandardResponse): - reviewer_id: Optional[int] = None +class AdminXASStandardResponse(XASStandardResponse): + submitter: Person + + +class XASStandardAdminReviewInput(SQLModel): reviewer_comments: Optional[str] = None - review_status: Optional[ReviewStatus] = None + review_status: ReviewStatus + standard_id: int + # get fedid from person table diff --git a/xas-standards-client/src/App.tsx b/xas-standards-client/src/App.tsx index f338def..005a007 100644 --- a/xas-standards-client/src/App.tsx +++ b/xas-standards-client/src/App.tsx @@ -1,9 +1,8 @@ import Header from "./components/Header.tsx"; import { Routes, Route } from "react-router-dom"; import StandardViewerMui from "./components/StandardViewer.tsx"; -import StandardSubmission from "./components/StandardSubmission.tsx"; +import StandardSubmission from "./components/submission/StandardSubmission.tsx"; import WelcomePage from "./components/WelcomePage.tsx"; -import BasicSelect from "./components/SelectTest.tsx"; import { MetadataProvider } from "./contexts/MetadataContext.tsx"; import { UserProvider } from "./contexts/UserContext.tsx"; @@ -18,6 +17,7 @@ import { Stack } from "@mui/material"; import { createTheme, ThemeProvider } from "@mui/material/styles"; import ColorModeContext from "./contexts/ColorModeContext.tsx"; +import ReviewPage from "./components/ReviewPage.tsx"; function App() { const [mode, setMode] = useState<"light" | "dark">("light"); @@ -50,7 +50,6 @@ function App() { } /> - } /> } /> } /> + } /> } /> {/* } /> */} diff --git a/xas-standards-client/src/components/Header.tsx b/xas-standards-client/src/components/Header.tsx index 929400d..05e6a92 100644 --- a/xas-standards-client/src/components/Header.tsx +++ b/xas-standards-client/src/components/Header.tsx @@ -44,6 +44,7 @@ export default function Header() { const user = useContext(UserContext); console.log(user); const loggedIn = user != null; + const admin = user != null && user.admin console.log(loggedIn); const navitems = { @@ -82,6 +83,9 @@ export default function Header() { {loggedIn && ( ) } + {admin && ( + + ) } diff --git a/xas-standards-client/src/components/ReviewCard.tsx b/xas-standards-client/src/components/ReviewCard.tsx new file mode 100644 index 0000000..5d179fd --- /dev/null +++ b/xas-standards-client/src/components/ReviewCard.tsx @@ -0,0 +1,82 @@ +import { TextField,Button, Card, CardContent, Typography, Box, FormControl, InputLabel, Stack, Select, MenuItem} from "@mui/material"; + +import { AdminXASStandard } from "../models"; + +import { useState } from "react"; +import axios from "axios"; +import { AxiosResponse, AxiosError } from "axios"; + +const review_statuses = ["pending", "approved", "rejected"] +const standards_url = "/api/standards"; + + + +export default function ReviewCard(props: {standard : AdminXASStandard}) { + + const [reviewStatus, setReviewStatus] = useState("pending"); + const [reviewerComments, setReviewerComments] = useState(""); + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + console.log("Reviewed") + + axios + .patch(standards_url, {reviewer_comments : reviewerComments, + review_status : reviewStatus, + standard_id : props.standard.id}) + .then((response: AxiosResponse) => { + console.log(response); + window.alert("Thank you for your review"); + }) + .catch((reason: AxiosError) => { + window.alert(reason.message); + }); + } + return ( + + + + Submitted by: {props.standard.submitter.identifier} + + + + + Review Status + + + setReviewerComments(e.target.value)} + /> + + + + + + ) +} \ No newline at end of file diff --git a/xas-standards-client/src/components/ReviewPage.tsx b/xas-standards-client/src/components/ReviewPage.tsx index 125866c..b00612e 100644 --- a/xas-standards-client/src/components/ReviewPage.tsx +++ b/xas-standards-client/src/components/ReviewPage.tsx @@ -1,8 +1,69 @@ +import ReviewTable from "./ReviewTable"; +import { Grid } from "@mui/material"; +import XASChart from "./StandardsChart"; +import axios from "axios"; + +import { useState } from "react"; + +import { XASData, AdminXASStandard } from "../models"; + +const data_url = "/api/data"; function ReviewPage() { - return
Review
; + const [standards, setStandardsList] = useState([]); + + const [xasdata, setXASData] = useState(null); + const [showTrans, setShowTrans] = useState(false); + const [showFluor, setShowFluor] = useState(false); + const [showRef, setShowRef] = useState(false); + const [contains, setContains] = useState([false, false, false]); + + + + function getData(setXASData: React.Dispatch) { + return (id: number) => { + axios.get(data_url + "/" + id).then((response) => { + const output: XASData = response.data as XASData; + const containsTrans = output != null && output.mutrans.length != 0; + const containsFluor = output != null && output.mufluor.length != 0; + const containsRef = output != null && output.murefer.length != 0; + + setShowTrans(containsTrans); + setShowRef(containsRef); + setShowFluor(containsFluor); + setContains([containsTrans, containsFluor, containsRef]); + + setXASData(output); + }); + }; + } + + const onClick = getData(setXASData); + return ( + + + + + + + + + ); } export default ReviewPage; diff --git a/xas-standards-client/src/components/ReviewTable.tsx b/xas-standards-client/src/components/ReviewTable.tsx index e69de29..7193766 100644 --- a/xas-standards-client/src/components/ReviewTable.tsx +++ b/xas-standards-client/src/components/ReviewTable.tsx @@ -0,0 +1,73 @@ +import StandardsTableView from "./StandardsTableView" +import StandardMetadataCard from "./StandardMetadataCard"; +import ReviewCard from "./ReviewCard"; + +import { Stack } from "@mui/material"; + +import {AdminXASStandard, XASStandard } from "../models"; + +import { useState, useEffect } from "react"; + +import axios from "axios"; + + +const standards_url = "/api/admin/standards"; + +const nResults = 7; + +export default function ReviewTable(props : { + standards: AdminXASStandard[]; + setStandards: React.Dispatch; + updatePlot: React.Dispatch; +}) { + + const [selectedStandard, setSelectedStandard] = useState(); + const [current, setCurrent] = useState(null); + const [prevNext, setPrevNext] = useState(null); + + const setStandards = props.setStandards; + useEffect(() => { + const get_req = (cursor: string | null) => { + let url = standards_url; + + + + url = url + "?size=" + String(nResults); + + + if (cursor) { + url = url + "&cursor=" + cursor; + } + + axios.get(url).then((response) => { + const output: AdminXASStandard[] = response.data.items as AdminXASStandard[]; + setPrevNext([response.data.previous_page, response.data.next_page]); + setStandards(output); + }); + }; + get_req(current); + }, [current, setStandards]); + + const stds: (AdminXASStandard | null)[] = [...props.standards]; + + if (props.standards.length < nResults) { + while (stds.length < nResults) { + stds.push(null); + } + } + return ( + + + {selectedStandard && <> + + + + } + + ) +} \ No newline at end of file diff --git a/xas-standards-client/src/components/SelectTest.tsx b/xas-standards-client/src/components/SelectTest.tsx deleted file mode 100644 index 890b5f7..0000000 --- a/xas-standards-client/src/components/SelectTest.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from "react"; -import Box from "@mui/material/Box"; -import InputLabel from "@mui/material/InputLabel"; -import MenuItem from "@mui/material/MenuItem"; -import FormControl from "@mui/material/FormControl"; -import Select, { SelectChangeEvent } from "@mui/material/Select"; - -export default function BasicSelect() { - const [age, setAge] = React.useState(""); - - const handleChange = (event: SelectChangeEvent) => { - console.log(event.target.value); - setAge(event.target.value); - }; - - return ( - - - Age - - - - ); -} diff --git a/xas-standards-client/src/components/StandardMetadataCard.tsx b/xas-standards-client/src/components/StandardMetadataCard.tsx index 3067865..e12325b 100644 --- a/xas-standards-client/src/components/StandardMetadataCard.tsx +++ b/xas-standards-client/src/components/StandardMetadataCard.tsx @@ -9,13 +9,9 @@ import { const data_url = "/api/data"; -function StandardMetadataCard(props: { standard: XASStandard | undefined }) { +function StandardMetadataCard(props: { standard: XASStandard }) { const standard = props.standard; - if (!standard) { - return ; - } - return ( diff --git a/xas-standards-client/src/components/StandardMetadataTable.tsx b/xas-standards-client/src/components/StandardMetadataTable.tsx index c4d9bb4..786c788 100644 --- a/xas-standards-client/src/components/StandardMetadataTable.tsx +++ b/xas-standards-client/src/components/StandardMetadataTable.tsx @@ -3,7 +3,6 @@ import { XASStandard } from "../models"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableContainer from "@mui/material/TableContainer"; -import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Paper from "@mui/material/Paper"; diff --git a/xas-standards-client/src/components/StandardsChart.tsx b/xas-standards-client/src/components/StandardsChart.tsx index 75e4762..bc7cd02 100644 --- a/xas-standards-client/src/components/StandardsChart.tsx +++ b/xas-standards-client/src/components/StandardsChart.tsx @@ -31,7 +31,7 @@ function CurveOption(props: { option: CurveType }) { } function XASChart(props: { - xasdata: XASData; + xasdata: XASData | null; showTrans: boolean; showFluor: boolean; showRef: boolean; @@ -124,8 +124,6 @@ function XASChart(props: { return ( ({ - [`&.${tableCellClasses.head}`]: { - backgroundColor: theme.palette.common.black, - color: theme.palette.common.white, - }, - [`&.${tableCellClasses.body}`]: { - fontSize: 14, - }, -})); - -const StyledTableRow = styled(TableRow)(({ theme }) => ({ - "&:nth-of-type(odd):not(:hover):not(.activeclicked)": { - backgroundColor: theme.palette.action.selected, - }, - - // hide last border - "&:last-child td, &:last-child th": { - border: 0, - }, -})); +import StandardsTableView from "./StandardsTableView"; const standards_url = "/api/standards"; const nResults = 7; -function StandardMetadata(props: { - key: number; - xasstandard: XASStandard | null; - selected: XASStandard | undefined; - updatePlot: React.Dispatch; - selectedRow: number; - setSelectedRow: React.Dispatch>; -}): JSX.Element { - const className = props.xasstandard === props.selected ? "activeclicked" : ""; - - return ( - { - props.setSelectedRow(props.key); - props.updatePlot(props.xasstandard!); - }} - key={props.key} - className={className} - hover={true} - selected={props.selectedRow === props.key} - sx={{ "&:last-child td, &:last-child th": { border: 0 } }} - > - - {props.xasstandard?.element.symbol ?? "\xa0"} - - - {props.xasstandard?.edge.name ?? ""} - - - {props.xasstandard?.sample_name ?? ""} - - - {props.xasstandard?.sample_prep ?? ""} - - - {props.xasstandard?.beamline.name ?? ""} - - - ); -} - function StandardsTable(props: { standards: XASStandard[]; elements: Element[]; @@ -94,27 +21,12 @@ function StandardsTable(props: { updatePlot: React.Dispatch; }): JSX.Element { const [selectedStandard, setSelectedStandard] = useState(); - const [prevNext, setPrevNext] = useState(null); - const [current, setCurrent] = useState(null); const [selectedElement, setSelectedElement] = useState(0); - const [selectedRow, setSelectedRow] = useState(-1); + const [current, setCurrent] = useState(null); + const [prevNext, setPrevNext] = useState(null); const setStandards = props.setStandards; const elements = props.elements; - - const clickStandard = (standard: XASStandard) => { - props.updatePlot(standard.id); - setSelectedStandard(standard); - }; - - const nextPage = () => { - setCurrent(prevNext[1]); - }; - - const prevPage = () => { - setCurrent(prevNext[0]); - }; - useEffect(() => { const get_req = (z: number, cursor: string | null) => { let url = standards_url; @@ -160,48 +72,15 @@ function StandardsTable(props: { selectedElement={selectedElement} setSelectedElement={setSelectedElement} /> - - - - - Element - Edge - Name - Prep - Beamline - - - - {stds.map((standard, key) => - StandardMetadata({ - key: key, - xasstandard: standard, - selected: selectedStandard, - updatePlot: clickStandard, - selectedRow: selectedRow, - setSelectedRow: setSelectedRow, - }) - )} - -
-
- - - - + + {selectedStandard && + }
); } diff --git a/xas-standards-client/src/components/StandardsTableView.tsx b/xas-standards-client/src/components/StandardsTableView.tsx new file mode 100644 index 0000000..1c5e608 --- /dev/null +++ b/xas-standards-client/src/components/StandardsTableView.tsx @@ -0,0 +1,155 @@ + +import { TableContainer, TableHead, TableRow, TableCell, Table, Paper, TableBody, Stack, Button } from "@mui/material" + +import { tableCellClasses } from "@mui/material/TableCell"; + +import { styled } from "@mui/material/styles"; +import { useState } from "react"; + +import { XASStandard } from "../models"; + + +const nResults = 7; + +const StyledTableCell = styled(TableCell)(({ theme }) => ({ + [`&.${tableCellClasses.head}`]: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + [`&.${tableCellClasses.body}`]: { + fontSize: 14, + }, + })); + + const StyledTableRow = styled(TableRow)(({ theme }) => ({ + "&:nth-of-type(odd):not(:hover):not(.activeclicked)": { + backgroundColor: theme.palette.action.selected, + }, + + // hide last border + "&:last-child td, &:last-child th": { + border: 0, + }, + })); + + + + + +function StandardMetadata(props: { + key: number; + xasstandard: XASStandard | null; + selected: XASStandard | undefined; + updatePlot: React.Dispatch; + selectedRow: number; + setSelectedRow: React.Dispatch>; + }): JSX.Element { + const className = props.xasstandard === props.selected ? "activeclicked" : ""; + + return ( + { + props.setSelectedRow(props.key); + props.updatePlot(props.xasstandard!); + }} + key={props.key} + className={className} + hover={true} + selected={props.selectedRow === props.key} + sx={{ "&:last-child td, &:last-child th": { border: 0 } }} + > + + {props.xasstandard?.element.symbol ?? "\xa0"} + + + {props.xasstandard?.edge.name ?? ""} + + + {props.xasstandard?.sample_name ?? ""} + + + {props.xasstandard?.sample_prep ?? ""} + + + {props.xasstandard?.beamline.name ?? ""} + + + ); + } + +export default function StandardsTableView(props : { + standards : XASStandard[]; + updatePlot : React.Dispatch; + selectedStandard: XASStandard | undefined; + setSelectedStandard : React.Dispatch>; + setCurrent: React.Dispatch>; + prevNext: string[] | null}) { + const [selectedRow, setSelectedRow] = useState(-1); + + const nextPage = () => { + props.setCurrent(props.prevNext[1]); + }; + + const prevPage = () => { + props.setCurrent(props.prevNext[0]); + }; + + const clickStandard = (standard: XASStandard) => { + props.updatePlot(standard.id); + props.setSelectedStandard(standard); + }; + + const stds: (XASStandard | null)[] = [...props.standards]; + + if (props.standards.length < nResults) { + while (stds.length < nResults) { + stds.push(null); + } + } + + return( + <> + + + + + Element + Edge + Name + Prep + Beamline + + + + {stds.map((standard, key) => + StandardMetadata({ + key: key, + xasstandard: standard, + selected: props.selectedStandard, + updatePlot: clickStandard, + selectedRow: selectedRow, + setSelectedRow: setSelectedRow, + }) + )} + +
+
+ + + + + + ) +} \ No newline at end of file diff --git a/xas-standards-client/src/components/AdditionalInfoForm.tsx b/xas-standards-client/src/components/submission/AdditionalInfoForm.tsx similarity index 100% rename from xas-standards-client/src/components/AdditionalInfoForm.tsx rename to xas-standards-client/src/components/submission/AdditionalInfoForm.tsx diff --git a/xas-standards-client/src/components/CitationForm.tsx b/xas-standards-client/src/components/submission/CitationForm.tsx similarity index 100% rename from xas-standards-client/src/components/CitationForm.tsx rename to xas-standards-client/src/components/submission/CitationForm.tsx diff --git a/xas-standards-client/src/components/ElementForm.tsx b/xas-standards-client/src/components/submission/ElementForm.tsx similarity index 97% rename from xas-standards-client/src/components/ElementForm.tsx rename to xas-standards-client/src/components/submission/ElementForm.tsx index 80df9e8..e0cb5a7 100644 --- a/xas-standards-client/src/components/ElementForm.tsx +++ b/xas-standards-client/src/components/submission/ElementForm.tsx @@ -1,6 +1,6 @@ import { Select, Box, MenuItem, FormControl, InputLabel, Stack } from "@mui/material"; -import { Element, Edge } from "../models"; +import { Element, Edge } from "../../models"; function ElementForm(props: { elementId: number; diff --git a/xas-standards-client/src/components/InstrumentForm.tsx b/xas-standards-client/src/components/submission/InstrumentForm.tsx similarity index 97% rename from xas-standards-client/src/components/InstrumentForm.tsx rename to xas-standards-client/src/components/submission/InstrumentForm.tsx index 9e40b7c..e418a5f 100644 --- a/xas-standards-client/src/components/InstrumentForm.tsx +++ b/xas-standards-client/src/components/submission/InstrumentForm.tsx @@ -8,7 +8,7 @@ import { TextField, } from "@mui/material"; -import { Beamline } from "../models"; +import { Beamline } from "../../models"; function InstrumentForm(props : { beamlines : Beamline[] diff --git a/xas-standards-client/src/components/SampleForm.tsx b/xas-standards-client/src/components/submission/SampleForm.tsx similarity index 100% rename from xas-standards-client/src/components/SampleForm.tsx rename to xas-standards-client/src/components/submission/SampleForm.tsx diff --git a/xas-standards-client/src/components/StandardSubmission.tsx b/xas-standards-client/src/components/submission/StandardSubmission.tsx similarity index 98% rename from xas-standards-client/src/components/StandardSubmission.tsx rename to xas-standards-client/src/components/submission/StandardSubmission.tsx index a516498..0712d5a 100644 --- a/xas-standards-client/src/components/StandardSubmission.tsx +++ b/xas-standards-client/src/components/submission/StandardSubmission.tsx @@ -1,11 +1,11 @@ import { useState, useContext } from "react"; -import { MetadataContext } from "../contexts/MetadataContext"; +import { MetadataContext } from "../../contexts/MetadataContext"; import axios from "axios"; import { AxiosResponse, AxiosError } from "axios"; -import XDIFile from "../xdifile"; +import XDIFile from "../../xdifile"; import ElementForm from "./ElementForm"; import SampleForm from "./SampleForm"; diff --git a/xas-standards-client/src/components/StandardSubmissionStepper.tsx b/xas-standards-client/src/components/submission/StandardSubmissionStepper.tsx similarity index 97% rename from xas-standards-client/src/components/StandardSubmissionStepper.tsx rename to xas-standards-client/src/components/submission/StandardSubmissionStepper.tsx index aca6392..ee023d8 100644 --- a/xas-standards-client/src/components/StandardSubmissionStepper.tsx +++ b/xas-standards-client/src/components/submission/StandardSubmissionStepper.tsx @@ -8,10 +8,10 @@ import { AxiosResponse, AxiosError } from "axios"; import XDIFile from "../xdifile"; import "./StandardSubmission.css"; -import ElementForm from "./ElementForm"; -import SampleForm from "./SampleForm"; -import InstrumentForm from "./InstrumentForm"; -import CitationForm from "./CitationForm"; +import ElementForm from "./submission/ElementForm"; +import SampleForm from "./submission/SampleForm"; +import InstrumentForm from "./submission/InstrumentForm"; +import CitationForm from "./submission/CitationForm"; import AdditionalInformationForm from "./AdditionalInfoForm"; import { UserContext } from "../contexts/UserContext"; diff --git a/xas-standards-client/src/components/VisuallyHiddenInput.tsx b/xas-standards-client/src/components/submission/VisuallyHiddenInput.tsx similarity index 100% rename from xas-standards-client/src/components/VisuallyHiddenInput.tsx rename to xas-standards-client/src/components/submission/VisuallyHiddenInput.tsx diff --git a/xas-standards-client/src/hooks/useUser.ts b/xas-standards-client/src/hooks/useUser.ts index 40d72e1..8d4b2c9 100644 --- a/xas-standards-client/src/hooks/useUser.ts +++ b/xas-standards-client/src/hooks/useUser.ts @@ -12,7 +12,7 @@ function useUser(): User | null { .get(user_url) .then((res: AxiosResponse) => { // console.log(res.status); - setCurrentUser({ identifier: res.data.user }); + setCurrentUser({ identifier: res.data.user, admin: res.data.admin}); }) .catch((error) => { console.log(error.response); diff --git a/xas-standards-client/src/main.tsx b/xas-standards-client/src/main.tsx index 5997f9a..29b2765 100644 --- a/xas-standards-client/src/main.tsx +++ b/xas-standards-client/src/main.tsx @@ -3,11 +3,15 @@ import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import App from "./App.tsx"; +import axios from "axios"; + async function enableMocking() { if (process.env.NODE_ENV !== "development") { return; } + axios.defaults.headers.common['Authorization'] = 'Bearer Test User' + const { worker } = await import("./mocks/browser"); // `worker.start()` returns a Promise that resolves diff --git a/xas-standards-client/src/mocks/handlers.ts b/xas-standards-client/src/mocks/handlers.ts index 85a3ddf..acd2b65 100644 --- a/xas-standards-client/src/mocks/handlers.ts +++ b/xas-standards-client/src/mocks/handlers.ts @@ -40,6 +40,7 @@ export const handlers = [ // return new HttpResponse(null, { status: 401 }); return HttpResponse.json({ user: "abc12345", + admin: true }); }), diff --git a/xas-standards-client/src/models.ts b/xas-standards-client/src/models.ts index 9b9b403..3a631fd 100644 --- a/xas-standards-client/src/models.ts +++ b/xas-standards-client/src/models.ts @@ -53,4 +53,5 @@ export interface AppMetadata { export interface User { identifier: string; + admin: boolean; } diff --git a/xas-standards-client/src/stories/CitationForm.stories.tsx b/xas-standards-client/src/stories/CitationForm.stories.tsx index 1d2f683..22970be 100644 --- a/xas-standards-client/src/stories/CitationForm.stories.tsx +++ b/xas-standards-client/src/stories/CitationForm.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { fn } from "@storybook/test"; -import CitationForm from "../components/CitationForm"; +import CitationForm from "../components/submission/CitationForm"; import "../components/StandardSubmission.css"; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export diff --git a/xas-standards-client/src/stories/ElementForm.stories.tsx b/xas-standards-client/src/stories/ElementForm.stories.tsx index 53c60c6..92f102c 100644 --- a/xas-standards-client/src/stories/ElementForm.stories.tsx +++ b/xas-standards-client/src/stories/ElementForm.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { fn } from "@storybook/test"; -import ElementForm from "../components/ElementForm"; +import ElementForm from "../components/submission/ElementForm"; import "../components/StandardSubmission.css"; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export diff --git a/xas-standards-client/src/stories/InstrumentForm.stories.tsx b/xas-standards-client/src/stories/InstrumentForm.stories.tsx index a4b4c1e..a7ae384 100644 --- a/xas-standards-client/src/stories/InstrumentForm.stories.tsx +++ b/xas-standards-client/src/stories/InstrumentForm.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { fn } from "@storybook/test"; -import InstrumentForm from "../components/InstrumentForm"; +import InstrumentForm from "../components/submission/InstrumentForm"; import "../components/StandardSubmission.css"; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export diff --git a/xas-standards-client/src/stories/SampleForm.stories.ts b/xas-standards-client/src/stories/SampleForm.stories.ts index 69e34a6..8d3e6c7 100644 --- a/xas-standards-client/src/stories/SampleForm.stories.ts +++ b/xas-standards-client/src/stories/SampleForm.stories.ts @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { fn } from "@storybook/test"; -import SampleForm from "../components/SampleForm"; +import SampleForm from "../components/submission/SampleForm"; import "../components/StandardSubmission.css"; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export