Compare commits
5 commits
master
...
mipca_repo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9de2f1285f | ||
|
|
eb01964622 | ||
|
|
bbdf40df47 | ||
|
|
6dbb7c3432 | ||
|
|
bced311508 |
11 changed files with 367 additions and 11 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -42,7 +42,7 @@
|
|||
"mui-tel-input": "^7.0.0",
|
||||
"notistack": "^3.0.1",
|
||||
"openweathermap-ts": "^1.2.10",
|
||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master",
|
||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#mipca_reports",
|
||||
"query-string": "^9.2.1",
|
||||
"react": "^18.3.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
|
|
@ -10911,7 +10911,7 @@
|
|||
},
|
||||
"node_modules/protobuf-ts": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#d0ac088df3822c10c0497f49173d1679b4e107b0",
|
||||
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#90d01d04404d6b38d3200634e194c17bae8a0737",
|
||||
"dependencies": {
|
||||
"protobufjs": "^6.8.8"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
"mui-tel-input": "^7.0.0",
|
||||
"notistack": "^3.0.1",
|
||||
"openweathermap-ts": "^1.2.10",
|
||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master",
|
||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#mipca_reports",
|
||||
"query-string": "^9.2.1",
|
||||
"react": "^18.3.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default function CableQuote(props: Props) {
|
|||
});
|
||||
|
||||
const pdfDoc = () => {
|
||||
return <AgPDFTemplate content={<PdfContent bin={bin} />} />;
|
||||
return <AgPDFTemplate content={[<PdfContent bin={bin} />]} />;
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Header from "./components/header";
|
|||
import Footer from "./components/footer";
|
||||
|
||||
interface Props {
|
||||
content: JSX.Element;
|
||||
content: JSX.Element[];
|
||||
}
|
||||
|
||||
export default function AgPDFTemplate(props: Props) {
|
||||
|
|
@ -27,15 +27,17 @@ export default function AgPDFTemplate(props: Props) {
|
|||
});
|
||||
return (
|
||||
<Document>
|
||||
<Page size={"A4"} style={pdfStyle.page}>
|
||||
{props.content.map(page => (
|
||||
<Page size={"A4"} style={pdfStyle.page}>
|
||||
<View style={pdfStyle.header}>
|
||||
<Header />
|
||||
</View>
|
||||
<View style={pdfStyle.content}>{props.content}</View>
|
||||
<View style={pdfStyle.content}>{page}</View>
|
||||
<View style={pdfStyle.footer}>
|
||||
<Footer />
|
||||
</View>
|
||||
</Page>
|
||||
))}
|
||||
</Document>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
31
src/common/pdfTemplates/MiPCA/components/footer.tsx
Normal file
31
src/common/pdfTemplates/MiPCA/components/footer.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { Image, StyleSheet, View } from "@react-pdf/renderer";
|
||||
import agFooter from "common/pdfTemplates/Agriculture/components/footerImages/agFooter.png";
|
||||
import React from "react";
|
||||
|
||||
export default function Footer() {
|
||||
const styles = StyleSheet.create({
|
||||
view: {
|
||||
flexDirection: "row",
|
||||
fontStyle: "normal",
|
||||
fontWeight: 650,
|
||||
borderTop: "1px solid black",
|
||||
padding: 10
|
||||
},
|
||||
label: {
|
||||
width: "20%"
|
||||
},
|
||||
email: {
|
||||
width: "40%"
|
||||
},
|
||||
phone: {
|
||||
width: "40%"
|
||||
}
|
||||
});
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={styles.view}>
|
||||
<Image src={agFooter} />
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
32
src/common/pdfTemplates/MiPCA/components/header.tsx
Normal file
32
src/common/pdfTemplates/MiPCA/components/header.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { Image, StyleSheet, Text, View } from "@react-pdf/renderer";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import logo from "assets/whitelabels/AdaptiveAgriculture/AgLogoText.png";
|
||||
|
||||
export default function Header() {
|
||||
const styles = StyleSheet.create({
|
||||
view: {
|
||||
flexDirection: "row",
|
||||
fontStyle: "normal",
|
||||
fontWeight: 650,
|
||||
borderBottom: "1px solid black"
|
||||
},
|
||||
logo: {
|
||||
width: "55%",
|
||||
textAlign: "left",
|
||||
paddingBottom: 15
|
||||
},
|
||||
estimate: {
|
||||
width: "45%",
|
||||
textAlign: "right"
|
||||
}
|
||||
});
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={styles.view}>
|
||||
<Image style={styles.logo} src={logo} />
|
||||
<Text style={styles.estimate}>{moment().format("YYYY-MM-DD")}</Text>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
43
src/common/pdfTemplates/MiPCA/mipcaPDFTemplate.tsx
Normal file
43
src/common/pdfTemplates/MiPCA/mipcaPDFTemplate.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { Document, Page, StyleSheet, View } from "@react-pdf/renderer";
|
||||
import Header from "./components/header";
|
||||
import Footer from "./components/footer";
|
||||
|
||||
interface Props {
|
||||
content: JSX.Element[];
|
||||
}
|
||||
|
||||
export default function MiPCAPDFTemplate(props: Props) {
|
||||
const pdfStyle = StyleSheet.create({
|
||||
page: {
|
||||
fontSize: 11,
|
||||
padding: "5%"
|
||||
},
|
||||
header: {
|
||||
flexDirection: "row",
|
||||
height: "8%"
|
||||
},
|
||||
content: {
|
||||
flexDirection: "row",
|
||||
height: "87%"
|
||||
},
|
||||
footer: {
|
||||
flexDirection: "row",
|
||||
height: "5%"
|
||||
}
|
||||
});
|
||||
return (
|
||||
<Document>
|
||||
{props.content.map(page => (
|
||||
<Page size={"A4"} style={pdfStyle.page}>
|
||||
<View style={pdfStyle.header}>
|
||||
<Header />
|
||||
</View>
|
||||
<View style={pdfStyle.content}>{page}</View>
|
||||
<View style={pdfStyle.footer}>
|
||||
<Footer />
|
||||
</View>
|
||||
</Page>
|
||||
))}
|
||||
</Document>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Gate } from "models/Gate";
|
||||
import { Box, Card, IconButton, List, ListItem, ListItemText, Theme, Typography } from "@mui/material";
|
||||
import { Box, Button, Card, IconButton, List, ListItem, ListItemText, Theme, Typography } from "@mui/material";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useMobile } from "hooks";
|
||||
|
|
@ -13,6 +13,7 @@ import GateSettings from "./GateSettings";
|
|||
import { useGateAPI, useGlobalState } from "providers";
|
||||
import { Settings } from "@mui/icons-material";
|
||||
import { cloneDeep } from "lodash";
|
||||
import GateReports from "reports/MiPCA/GateReports";
|
||||
|
||||
interface Props {
|
||||
//gates: Gate[];
|
||||
|
|
@ -54,6 +55,7 @@ export default function GateList(props: Props) {
|
|||
const [gates, setGates] = useState<Gate[]>([])
|
||||
const [selectedGate, setSelectedGate] = useState<Gate | undefined>()
|
||||
const [search, setSearch] = useState("")
|
||||
const [openReports, setOpenReports] = useState(false)
|
||||
|
||||
const goToGate = (gate: Gate) => {
|
||||
let path = "/terminals/" + gate.key;
|
||||
|
|
@ -208,6 +210,8 @@ export default function GateList(props: Props) {
|
|||
const gateTable = () => {
|
||||
return (
|
||||
<ResponsiveTable<Gate>
|
||||
actions={<Button onClick={()=>{setOpenReports(true)}}>Generate Reports</Button>}
|
||||
title="Terminal Gates"
|
||||
page={tablePage}
|
||||
pageSize={pageSize}
|
||||
columns={isMobile || props.useMobile ? mobileCols() : desktopCols()}
|
||||
|
|
@ -237,7 +241,8 @@ export default function GateList(props: Props) {
|
|||
)
|
||||
};
|
||||
|
||||
return <Box>
|
||||
return (
|
||||
<React.Fragment>
|
||||
<GateSettings
|
||||
open={gateDialog}
|
||||
gate={selectedGate}
|
||||
|
|
@ -256,5 +261,7 @@ export default function GateList(props: Props) {
|
|||
pulse={gates.length < 1}
|
||||
/>
|
||||
{gateTable()}
|
||||
</Box>
|
||||
<GateReports open={openReports} onClose={()=>{setOpenReports(false)}}/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ export interface IGateInterface {
|
|||
end: string,
|
||||
otherTeam?: string
|
||||
) => Promise<AxiosResponse<pond.ListGateMeasurementsResponse>>;
|
||||
//new call for getting report data for gates
|
||||
gateReportData: (gates: string[], start: string, end: string, otherTeam?: string) => Promise<AxiosResponse<pond.GateReportDataResponse>>
|
||||
}
|
||||
|
||||
export const GateAPIcontext = createContext<IGateInterface>({} as IGateInterface);
|
||||
|
|
@ -347,6 +349,24 @@ export default function GateProvider(props: PropsWithChildren<Props>) {
|
|||
);
|
||||
};
|
||||
|
||||
const gateReportData = (
|
||||
gates: string[],
|
||||
start: string,
|
||||
end: string,
|
||||
otherTeam?: string
|
||||
) => {
|
||||
const view = otherTeam ? otherTeam : as
|
||||
let url = pondURL("/reports/gates?gateKeys=" + gates.toString() + "&start=" + start + "&end=" + end + "&as=" + view)
|
||||
return new Promise<AxiosResponse<pond.GateReportDataResponse>>((resolve, reject) => {
|
||||
get<pond.GateReportDataResponse>(url).then(resp => {
|
||||
resp.data = pond.GateReportDataResponse.fromObject(resp.data)
|
||||
return resolve(resp)
|
||||
}).catch(err => {
|
||||
return reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<GateAPIcontext.Provider
|
||||
value={{
|
||||
|
|
@ -360,7 +380,8 @@ export default function GateProvider(props: PropsWithChildren<Props>) {
|
|||
updatePrefs,
|
||||
listGateAirflow,
|
||||
listGateMeasurements,
|
||||
listGateFlowEvents
|
||||
listGateFlowEvents,
|
||||
gateReportData
|
||||
}}>
|
||||
{children}
|
||||
</GateAPIcontext.Provider>
|
||||
|
|
|
|||
191
src/reports/MiPCA/GateReports.tsx
Normal file
191
src/reports/MiPCA/GateReports.tsx
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import { Box, Button, CircularProgress, DialogActions, DialogContent, DialogTitle, Typography } from "@mui/material"
|
||||
import MiPCAPDFTemplate from "common/pdfTemplates/MiPCA/mipcaPDFTemplate"
|
||||
import ResponsiveDialog from "common/ResponsiveDialog"
|
||||
import ResponsiveTable from "common/ResponsiveTable"
|
||||
import { GetDefaultDateRange } from "common/time/DateRange"
|
||||
import DateSelect from "common/time/DateSelect"
|
||||
import { cloneDeep } from "lodash"
|
||||
import { Gate } from "models/Gate"
|
||||
import { Moment } from "moment"
|
||||
import { pond } from "protobuf-ts/pond"
|
||||
import { useGateAPI } from "providers"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import GateReportPDF from "./pdfComponents.tsx/GateReportPDF"
|
||||
import { PDFViewer, StyleSheet } from "@react-pdf/renderer"
|
||||
import { useMobile } from "hooks"
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
gate?: string //if the gate key is passed in just generate for that gate, otherwise give the user a way of selecting which gates
|
||||
}
|
||||
|
||||
export default function GateReports(props: Props){
|
||||
const {gate, open, onClose} = props
|
||||
const gateAPI = useGateAPI()
|
||||
const [allGates, setAllGates] = useState<Gate[]>([])
|
||||
const [displayedGates, setDisplayedGates] = useState<Gate[]>([])
|
||||
const [pagesize, setPageSize] = useState(10)
|
||||
const [page, setPage] = useState(0)
|
||||
const [gateKeys, setGateKeys] = useState<string[]>([])
|
||||
const [selectedTableRows,setSelectedTableRows] = useState<number[]>([])
|
||||
const [reportPages, setReportPages] = useState<JSX.Element[]>([])
|
||||
const [loadingReportData, setLoadingReportData] = useState(false)
|
||||
const defaultDateRange = GetDefaultDateRange();
|
||||
const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
|
||||
const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
|
||||
const isMobile = useMobile()
|
||||
const pdfStyle = StyleSheet.create({
|
||||
viewerDesktop: {
|
||||
height: 500 * 1.44,
|
||||
width: 500
|
||||
},
|
||||
viewerMobile: {
|
||||
height: "100%",
|
||||
width: "100%"
|
||||
}
|
||||
});
|
||||
|
||||
const closeDialog = () => {
|
||||
setGateKeys([])
|
||||
setSelectedTableRows([])
|
||||
setReportPages([])
|
||||
onClose()
|
||||
}
|
||||
|
||||
//load all of the gates for the user/team
|
||||
useEffect(()=>{
|
||||
if(open){
|
||||
if(gate) {
|
||||
setGateKeys([gate])
|
||||
return
|
||||
} else {
|
||||
gateAPI.listGates(0, 0).then(resp => {
|
||||
if(resp.data.gates){
|
||||
setAllGates(resp.data.gates.map(g => Gate.create(g)))
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
},[gate, open])
|
||||
|
||||
useEffect(()=>{
|
||||
let start = page*pagesize
|
||||
let offset = pagesize
|
||||
setDisplayedGates(allGates.slice(start, start+offset))
|
||||
},[allGates, page, pagesize])
|
||||
|
||||
//selection table to select which gates to generate a report
|
||||
const gateTable = () => {
|
||||
return (
|
||||
<ResponsiveTable<Gate>
|
||||
columns={[
|
||||
{
|
||||
title: "Gate",
|
||||
render: row => <Typography>{row.name}</Typography>
|
||||
}
|
||||
]}
|
||||
selectedRows={selectedTableRows}
|
||||
rowSelect={(gate, tableIndex, checked) => {
|
||||
let rowIndexes = cloneDeep(selectedTableRows)
|
||||
let keys = cloneDeep(gateKeys)
|
||||
if(checked){
|
||||
if(!selectedTableRows.includes(tableIndex)) rowIndexes.push(tableIndex)
|
||||
if(!gateKeys.includes(gate.key)) keys.push(gate.key)
|
||||
}else{
|
||||
if(rowIndexes.includes(tableIndex)) rowIndexes.splice(rowIndexes.indexOf(tableIndex), 1)
|
||||
if(keys.includes(gate.key)) keys.splice(keys.indexOf(gate.key), 1)
|
||||
}
|
||||
setSelectedTableRows(rowIndexes)
|
||||
setGateKeys(keys)
|
||||
}}
|
||||
rows={displayedGates}
|
||||
total={allGates.length}
|
||||
pageSize={pagesize}
|
||||
page={page}
|
||||
setPage={(page) => {
|
||||
setPage(page)
|
||||
}}
|
||||
handleRowsPerPageChange={(event) => {
|
||||
setPageSize(event.target.value)
|
||||
}} />
|
||||
)
|
||||
}
|
||||
|
||||
//two text field select boxes to select the start and end dates
|
||||
const dateSelect = () => {
|
||||
return (
|
||||
<DateSelect
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
updateDateRange={(start, end) => {
|
||||
setStartDate(start)
|
||||
setEndDate(end)
|
||||
}}
|
||||
label="Build Reports For"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* new api call for gates that takes in an array of gate keys to build the report data for
|
||||
* this call will return an array of the gate report data
|
||||
* in the return loop through the data building a pdf report for each one
|
||||
*/
|
||||
const generateReports = () => {
|
||||
setLoadingReportData(true)
|
||||
gateAPI.gateReportData(gateKeys, startDate.toISOString(), endDate.toISOString()).then(resp => {
|
||||
console.log(resp)
|
||||
let pdfPages: JSX.Element[] = []
|
||||
if(resp.data.data){
|
||||
resp.data.data.forEach(reportData => {
|
||||
pdfPages.push(<GateReportPDF reportData={reportData}/>)
|
||||
})
|
||||
}
|
||||
setReportPages(pdfPages)
|
||||
}).catch(err => {
|
||||
|
||||
}).finally(()=>{
|
||||
//in the finally set the loading to false
|
||||
setLoadingReportData(false)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveDialog open={open} onClose={() => {closeDialog()}}>
|
||||
<DialogTitle>Gate Reports</DialogTitle>
|
||||
<DialogContent>
|
||||
{loadingReportData ?
|
||||
<Box display="flex" justifyContent="center">
|
||||
<CircularProgress color="primary" size={100}/>
|
||||
</Box>
|
||||
:
|
||||
<React.Fragment>
|
||||
{reportPages.length > 0 ?
|
||||
<Box>
|
||||
<PDFViewer style={isMobile ? pdfStyle.viewerMobile : pdfStyle.viewerDesktop}>
|
||||
<MiPCAPDFTemplate content={reportPages}/>
|
||||
</PDFViewer>
|
||||
</Box> :
|
||||
<Box>
|
||||
{dateSelect()}
|
||||
{!gate && gateTable()}
|
||||
</Box>
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={()=>{
|
||||
closeDialog()
|
||||
}}>Close</Button>
|
||||
<Button variant="contained" color="primary" disabled={loadingReportData} onClick={()=>{
|
||||
generateReports()
|
||||
}}>Generate Reports</Button>
|
||||
</DialogActions>
|
||||
</ResponsiveDialog>
|
||||
)
|
||||
}
|
||||
29
src/reports/MiPCA/pdfComponents.tsx/GateReportPDF.tsx
Normal file
29
src/reports/MiPCA/pdfComponents.tsx/GateReportPDF.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { View, StyleSheet } from "@react-pdf/renderer";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
|
||||
interface Props {
|
||||
reportData: pond.GateReportData
|
||||
}
|
||||
|
||||
export default function GateReportPDF(props: Props){
|
||||
const styles = StyleSheet.create({
|
||||
general: {
|
||||
marginTop: 2,
|
||||
flexDirection: "row",
|
||||
borderTop: "1px solid black",
|
||||
borderBottom: "1px solid black",
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
fontFamily: "Helvetica-Bold",
|
||||
fontSize: 20,
|
||||
textAlign: "center"
|
||||
}
|
||||
});
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.general}>
|
||||
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue