finished the mobile view for the field page and some minor functionality changes
This commit is contained in:
parent
06da0aad25
commit
a2971c6e44
4 changed files with 246 additions and 43 deletions
|
|
@ -152,9 +152,7 @@ export default function FieldSettings(props: Props) {
|
||||||
|
|
||||||
const fieldInformation = () => {
|
const fieldInformation = () => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<React.Fragment>
|
||||||
<Box>
|
|
||||||
<DialogTitle>Field Information</DialogTitle>
|
|
||||||
<TextField
|
<TextField
|
||||||
value={fieldName}
|
value={fieldName}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
|
|
@ -258,7 +256,14 @@ export default function FieldSettings(props: Props) {
|
||||||
Field Colour
|
Field Colour
|
||||||
<ColourPicker onChange={color => setFeatColor(color)} />
|
<ColourPicker onChange={color => setFeatColor(color)} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog fullWidth open={props.open} onClose={onClose}>
|
||||||
|
<DialogTitle>Field Information</DialogTitle>
|
||||||
|
<DialogContent>{fieldInformation()}</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Grid container direction="row">
|
<Grid container direction="row">
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|
@ -282,13 +287,6 @@ export default function FieldSettings(props: Props) {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ResponsiveDialog fullWidth fullScreen={false} open={props.open} onClose={onClose}>
|
|
||||||
<DialogContent>{fieldInformation()}</DialogContent>
|
|
||||||
</ResponsiveDialog>
|
</ResponsiveDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,29 @@
|
||||||
import { Box, Typography } from "@mui/material"
|
import { ExpandMore } from "@mui/icons-material"
|
||||||
|
import { Accordion, AccordionDetails, AccordionSummary, Box, Theme, Typography } from "@mui/material"
|
||||||
|
import { makeStyles } from "@mui/styles"
|
||||||
import ResponsiveTable, { Column } from "common/ResponsiveTable"
|
import ResponsiveTable, { Column } from "common/ResponsiveTable"
|
||||||
import GrainDescriber from "grain/GrainDescriber"
|
import GrainDescriber from "grain/GrainDescriber"
|
||||||
import { HarvestPlan } from "models"
|
import { cloneDeep } from "lodash"
|
||||||
|
import { Field, HarvestPlan } from "models"
|
||||||
import { useHarvestPlanAPI } from "providers"
|
import { useHarvestPlanAPI } from "providers"
|
||||||
import { useCallback, useEffect, useState } from "react"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
import { getThemeType } from "theme"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
field?: string
|
field: Field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
dark: {
|
||||||
|
backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(50, 50, 50)",
|
||||||
|
padding: 5
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
backgroundColor: getThemeType() === "light" ? "rgb(235, 235, 235)" : "rgb(60, 60, 60)",
|
||||||
|
padding: 5
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
export default function HarvestPlanTable(props: Props){
|
export default function HarvestPlanTable(props: Props){
|
||||||
const {field} = props
|
const {field} = props
|
||||||
const harvestAPI = useHarvestPlanAPI()
|
const harvestAPI = useHarvestPlanAPI()
|
||||||
|
|
@ -15,9 +31,10 @@ export default function HarvestPlanTable(props: Props){
|
||||||
const [pageSize, setPageSize] = useState(10)
|
const [pageSize, setPageSize] = useState(10)
|
||||||
const [loadedPlans, setLoadedPlans] = useState<HarvestPlan[]>([])
|
const [loadedPlans, setLoadedPlans] = useState<HarvestPlan[]>([])
|
||||||
const [total, setTotal] = useState(0)
|
const [total, setTotal] = useState(0)
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
harvestAPI.listHarvestPlans(pageSize, page*pageSize, "asc", "harvestYear", field).then(resp => {
|
harvestAPI.listHarvestPlans(pageSize, page*pageSize, "desc", "harvestYear", field.key()).then(resp => {
|
||||||
if(resp.data.harvestPlan){
|
if(resp.data.harvestPlan){
|
||||||
setLoadedPlans(resp.data.harvestPlan.map(hp => HarvestPlan.create(hp)))
|
setLoadedPlans(resp.data.harvestPlan.map(hp => HarvestPlan.create(hp)))
|
||||||
}
|
}
|
||||||
|
|
@ -106,6 +123,80 @@ export default function HarvestPlanTable(props: Props){
|
||||||
setPage={(page)=>{
|
setPage={(page)=>{
|
||||||
setPage(page)
|
setPage(page)
|
||||||
}}
|
}}
|
||||||
|
loadMore={() => {
|
||||||
|
harvestAPI.listHarvestPlans(pageSize, loadedPlans.length, "desc", "harvestYear", field.key()).then(resp => {
|
||||||
|
let currentPlans = cloneDeep(loadedPlans)
|
||||||
|
let newPlans: HarvestPlan[] = []
|
||||||
|
if(resp.data.harvestPlan){
|
||||||
|
newPlans = resp.data.harvestPlan.map(hp => HarvestPlan.create(hp))
|
||||||
|
}
|
||||||
|
setLoadedPlans(currentPlans.concat(newPlans))
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
renderMobile={(row, index) => {
|
||||||
|
// since these are more about previous harvest plans i am only showing the final tallys rather than the breakdowns
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||||
|
<Typography sx={{fontWeight: 650}}>
|
||||||
|
{row.settings.title} - {row.harvestYear()}
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
{/* grain */}
|
||||||
|
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Grain:</Typography>
|
||||||
|
<Typography variant="subtitle1">{GrainDescriber(row.cropType()).name}</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* variant */}
|
||||||
|
<Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Variant:</Typography>
|
||||||
|
<Typography variant="subtitle1">{row.grainType()}</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* yield target */}
|
||||||
|
{/* <Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Yield Target:</Typography>
|
||||||
|
<Typography variant="subtitle1">{row.yieldTarget()}bu</Typography>
|
||||||
|
</Box> */}
|
||||||
|
{/* actual yield */}
|
||||||
|
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Actual Yield:</Typography>
|
||||||
|
<Typography variant="subtitle1">{row.actualYield()}bu</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* bushel price */}
|
||||||
|
<Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Bushel Price:</Typography>
|
||||||
|
<Typography variant="subtitle1">${row.bushelPrice()}</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* material cost */}
|
||||||
|
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Material Cost:</Typography>
|
||||||
|
<Typography variant="subtitle1">${row.totalMaterialCost() * field.acres()}</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* equipment cost */}
|
||||||
|
<Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Equipment Cost:</Typography>
|
||||||
|
<Typography variant="subtitle1">${row.totalEquipmentCost() * field.acres()}</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* pre-seed materials */}
|
||||||
|
{/* pre-seed equpment */}
|
||||||
|
{/* seed materials */}
|
||||||
|
{/* seed equpment */}
|
||||||
|
{/* post-seed materials */}
|
||||||
|
{/* post-seed equpment */}
|
||||||
|
{/* harvest materials */}
|
||||||
|
{/* harvest equpment */}
|
||||||
|
{/* fall materials */}
|
||||||
|
{/* fall equpment */}
|
||||||
|
{/* total cost */}
|
||||||
|
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Total Cost:</Typography>
|
||||||
|
<Typography variant="subtitle1">${(row.totalEquipmentCost() + row.totalMaterialCost()) * field.acres()}</Typography>
|
||||||
|
</Box>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -42,11 +42,11 @@ const featureVersions: Map<string, FeatureVersionByPlatform> = new Map([
|
||||||
v2Cell: "N/A",
|
v2Cell: "N/A",
|
||||||
v2WifiS3: "N/A",
|
v2WifiS3: "N/A",
|
||||||
v2CellS3: "N/A",
|
v2CellS3: "N/A",
|
||||||
v2CellBlack: "2.1.6",
|
v2CellBlack: "2.1.7",
|
||||||
v2CellGreen: "N/A",
|
v2CellGreen: "N/A",
|
||||||
v2WifiBlue: "2.1.6",
|
v2WifiBlue: "2.1.7",
|
||||||
v2CellBlue: "2.1.6",
|
v2CellBlue: "2.1.7",
|
||||||
v2EthBlue: "2.1.6"
|
v2EthBlue: "2.1.7"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Field, fieldScope, HarvestPlan, teamScope } from "models";
|
import { Field, fieldScope, HarvestPlan, teamScope } from "models";
|
||||||
import { useFieldAPI, useGlobalState, useHarvestPlanAPI } from "providers";
|
import { useFieldAPI, useGlobalState, useHarvestPlanAPI } from "providers";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { useLocation, useParams } from "react-router-dom";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import PageContainer from "./PageContainer";
|
import PageContainer from "./PageContainer";
|
||||||
import { useMobile, useSnackbar, useUserAPI } from "hooks";
|
import { useMobile, useSnackbar, useUserAPI } from "hooks";
|
||||||
import { Box, Card, Grid2, IconButton } from "@mui/material";
|
import { Box, Card, Grid2, IconButton, Tab, Tabs, Theme, Typography, darken } from "@mui/material";
|
||||||
import FieldActions from "field/FieldActions";
|
import FieldActions from "field/FieldActions";
|
||||||
import { Settings } from "@mui/icons-material";
|
import { Settings } from "@mui/icons-material";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
|
@ -14,10 +14,47 @@ import TaskViewer from "tasks/TaskViewer";
|
||||||
import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay";
|
import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import HarvestPlanTable from "harvestPlan/HarvestPlanTable";
|
import HarvestPlanTable from "harvestPlan/HarvestPlanTable";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import FieldSettings from "field/FieldSettings";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
return ({
|
||||||
|
card: {
|
||||||
|
height: "100%",
|
||||||
|
bgcolor: darken(theme.palette.background.paper, 0.05),
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
fontSize: 35,
|
||||||
|
fontWeight: 650
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
interface TabPanelProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
index: any;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabPanel(props: TabPanelProps) {
|
||||||
|
const { children, value, index, ...other } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
aria-labelledby={`simple-tab-${index}`}
|
||||||
|
style={{ height: "94%", paddingTop: "15px" }}
|
||||||
|
{...other}>
|
||||||
|
{value === index && <React.Fragment>{children}</React.Fragment>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function FieldPage() {
|
export default function FieldPage() {
|
||||||
const { state } = useLocation();
|
const { state } = useLocation();
|
||||||
//const [{ as }] = useGlobalState()
|
//const [{ as }] = useGlobalState()
|
||||||
|
const classes = useStyles();
|
||||||
const fieldAPI = useFieldAPI();
|
const fieldAPI = useFieldAPI();
|
||||||
const userAPI = useUserAPI();
|
const userAPI = useUserAPI();
|
||||||
const [{as, user}] = useGlobalState();
|
const [{as, user}] = useGlobalState();
|
||||||
|
|
@ -31,11 +68,23 @@ export default function FieldPage() {
|
||||||
const [hPlan, setHPlan] = useState<HarvestPlan>(HarvestPlan.create());
|
const [hPlan, setHPlan] = useState<HarvestPlan>(HarvestPlan.create());
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const hPlanAPI = useHarvestPlanAPI();
|
const hPlanAPI = useHarvestPlanAPI();
|
||||||
|
const [tabVal, setTabVal] = useState(0)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const loadField = useCallback(() => {
|
const loadField = useCallback(() => {
|
||||||
|
if(state.field){
|
||||||
|
fieldAPI.getField(state.field).then(resp => {
|
||||||
|
setField(Field.create(resp.data))
|
||||||
|
}).catch(err => {
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
},[as])
|
},[as])
|
||||||
|
|
||||||
|
const goTo = () => {
|
||||||
|
navigate("/fields");
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
// if already loading do nothing
|
// if already loading do nothing
|
||||||
if(loading) return
|
if(loading) return
|
||||||
|
|
@ -91,13 +140,20 @@ export default function FieldPage() {
|
||||||
|
|
||||||
const fieldActions = () => {
|
const fieldActions = () => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Grid2 container direction={"row"} justifyContent="space-between" alignContent="center" alignItems="center">
|
||||||
<IconButton
|
<Grid2>
|
||||||
onClick={() => {
|
<Typography className={classes.sectionHeader}>
|
||||||
setOpenFieldSettings(true)
|
{field.name()}
|
||||||
}}><Settings /></IconButton>
|
</Typography>
|
||||||
<FieldActions field={field} permissions={permissions} refreshCallback={()=>{}}/>
|
</Grid2>
|
||||||
</Box>
|
<Grid2>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setOpenFieldSettings(true)
|
||||||
|
}}><Settings /></IconButton>
|
||||||
|
<FieldActions field={field} permissions={permissions} refreshCallback={()=>{}}/>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,43 +190,44 @@ export default function FieldPage() {
|
||||||
field.key() !== "" && taskLoadKeys.push(field.key());
|
field.key() !== "" && taskLoadKeys.push(field.key());
|
||||||
//hPlan.key() !== "" && taskLoadKeys.push(hPlan.key());
|
//hPlan.key() !== "" && taskLoadKeys.push(hPlan.key());
|
||||||
}
|
}
|
||||||
return (<TaskViewer drawerView objectKey={field.key()} loadKeys={taskLoadKeys} />)
|
return (<TaskViewer drawerView objectKey={field.key()} loadKeys={taskLoadKeys} overlayButton={isMobile}/>)
|
||||||
}
|
}
|
||||||
|
|
||||||
const desktopView = () => {
|
const desktopView = () => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Box>
|
<Box>
|
||||||
{field.name()}
|
|
||||||
<Grid2 container spacing={1}>
|
<Grid2 container spacing={1}>
|
||||||
<Grid2 size={4}>
|
<Grid2 size={4}>
|
||||||
<Card raised sx={{height: "100%"}}>
|
<Card raised className={classes.card}>
|
||||||
{map()}
|
{map()}
|
||||||
</Card>
|
</Card>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2 size={3}>
|
<Grid2 size={3}>
|
||||||
<Card raised sx={{height: "100%"}}>
|
<Card raised className={classes.card}>
|
||||||
{weather()}
|
{weather()}
|
||||||
</Card>
|
</Card>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2 size={5}>
|
<Grid2 size={5}>
|
||||||
<Card raised sx={{height: "100%"}}>
|
<Card raised className={classes.card}>
|
||||||
{tasks()}
|
{tasks()}
|
||||||
</Card>
|
</Card>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
Harvest Overview
|
<Typography className={classes.sectionHeader}>
|
||||||
|
Harvest Overview
|
||||||
|
</Typography>
|
||||||
<Grid2 container spacing={1}>
|
<Grid2 container spacing={1}>
|
||||||
<Grid2 size={4}>
|
<Grid2 size={4}>
|
||||||
<Card raised>
|
<Card raised className={classes.card}>
|
||||||
{harvestPlans()}
|
{harvestPlans()}
|
||||||
</Card>
|
</Card>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2 size={8}>
|
<Grid2 size={8}>
|
||||||
<Card>
|
<Card raised>
|
||||||
<HarvestPlanTable field={field.key()}/>
|
<HarvestPlanTable field={field}/>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
@ -181,12 +238,68 @@ export default function FieldPage() {
|
||||||
|
|
||||||
const mobileView = () => {
|
const mobileView = () => {
|
||||||
return (
|
return (
|
||||||
<Box>Mobile will just be the drawer view</Box>
|
<React.Fragment>
|
||||||
|
<Tabs
|
||||||
|
value={tabVal}
|
||||||
|
onChange={(_, val) => {
|
||||||
|
setTabVal(val)
|
||||||
|
}}
|
||||||
|
TabIndicatorProps={{ style: { background: "rgba(255,255,0,255)" } }}>
|
||||||
|
<Tab label="Overview" />
|
||||||
|
<Tab label="Activities" />
|
||||||
|
<Tab label="Harvest" />
|
||||||
|
</Tabs>
|
||||||
|
<TabPanel value={tabVal} index={0}>
|
||||||
|
<Grid2 container spacing={2} direction="column">
|
||||||
|
<Grid2 size={12}>
|
||||||
|
<Card raised sx={{height: "50vh"}}>
|
||||||
|
{map()}
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 size={12}>
|
||||||
|
<Card raised>
|
||||||
|
{weather()}
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={tabVal} index={1}>
|
||||||
|
<Card raised>
|
||||||
|
{tasks()}
|
||||||
|
</Card>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={tabVal} index={2}>
|
||||||
|
<Grid2>
|
||||||
|
<Grid2 size={12}>
|
||||||
|
<Card raised className={classes.card}>
|
||||||
|
{harvestPlans()}
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<HarvestPlanTable field={field}/>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</TabPanel>
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldSettings = () => {
|
const fieldSettings = () => {
|
||||||
|
return (
|
||||||
|
<FieldSettings
|
||||||
|
open={openFieldSettings}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenFieldSettings(false)
|
||||||
|
}}
|
||||||
|
selectedField={field}
|
||||||
|
updateFields={() => {
|
||||||
|
loadField()
|
||||||
|
}}
|
||||||
|
removeField={()=>{
|
||||||
|
goTo()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -195,6 +308,7 @@ export default function FieldPage() {
|
||||||
{fieldActions()}
|
{fieldActions()}
|
||||||
{isMobile ? mobileView() : desktopView()}
|
{isMobile ? mobileView() : desktopView()}
|
||||||
</Box>
|
</Box>
|
||||||
|
{fieldSettings()}
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue