-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from openimis/reports
Reports
- Loading branch information
Showing
13 changed files
with
503 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle } from "@material-ui/core"; | ||
import { ErrorBoundary, SelectInput, useBoolean, useModulesManager, useTranslations } from "@openimis/fe-core"; | ||
import React, { useEffect, useState } from "react"; | ||
import { useReportQuery } from "../hooks"; | ||
import ReportPicker from "./ReportPicker"; | ||
|
||
const GenerateReportPicker = (props) => { | ||
const { name, outputFormat = "pdf", children } = props; | ||
const modulesManager = useModulesManager(); | ||
const { formatMessage } = useTranslations("tools.GenerateReportPicker", modulesManager); | ||
const [isOpen, { toggle }] = useBoolean(false); | ||
const [values, setValues] = useState({ outputFormat }); | ||
const reportQuery = useReportQuery({ name }, { skip: !name }); | ||
const [report, setReport] = useState(); | ||
const moduleReport = modulesManager.getReport(report?.name); | ||
|
||
useEffect(() => { | ||
if (reportQuery?.report) { | ||
setReport(reportQuery?.report); | ||
} | ||
}, [reportQuery]); | ||
|
||
const onSubmit = () => { | ||
const query = new URLSearchParams(); | ||
for (const [key, value] of Object.entries(moduleReport.getParams(values))) { | ||
query.set(key, value); | ||
} | ||
window.open(`/api/report/${report.name}/${values.outputFormat}/?${query.toString()}`, "_blank", "download"); | ||
toggle(); | ||
}; | ||
const isValid = Boolean(report && values.outputFormat && moduleReport?.isValid && moduleReport.isValid(values)); | ||
return ( | ||
<> | ||
{children ? ( | ||
children({ toggle }) | ||
) : ( | ||
<Button onClick={toggle} variant="contained" size="small"> | ||
{formatMessage("triggerBtn")} | ||
</Button> | ||
)} | ||
<Dialog open={isOpen} onClose={toggle}> | ||
<DialogTitle>{formatMessage("title")}</DialogTitle> | ||
<DialogContent> | ||
<ErrorBoundary> | ||
{!name && ( | ||
<Box mb={2}> | ||
<ReportPicker value={report} onChange={setReport} /> | ||
</Box> | ||
)} | ||
{!props.outputFormat && ( | ||
<Box mb={1}> | ||
<SelectInput | ||
module="tools" | ||
required | ||
label="GenerateReportPicker.formatLabel" | ||
options={[ | ||
{ value: "pdf", label: "PDF" }, | ||
{ value: "xlsx", label: "XLSX" }, | ||
]} | ||
value={values.outputFormat} | ||
onChange={(outputFormat) => setValues({ ...values, outputFormat })} | ||
/> | ||
</Box> | ||
)} | ||
{moduleReport?.component && ( | ||
<moduleReport.component report={report} values={values} setValues={setValues} /> | ||
)} | ||
</ErrorBoundary> | ||
</DialogContent> | ||
<DialogActions> | ||
<Button onClick={toggle}>{formatMessage("cancelBtn")}</Button> | ||
<Button disabled={!isValid} onClick={onSubmit}> | ||
{formatMessage("generateBtn")} | ||
</Button> | ||
</DialogActions> | ||
</Dialog> | ||
</> | ||
); | ||
}; | ||
|
||
export default GenerateReportPicker; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import React, { useState, useCallback, useRef, useEffect } from "react"; | ||
|
||
const ReportBro = (props) => { | ||
const { definition, onChange, ...delegated } = props; | ||
const [isReady, setReady] = useState(false); | ||
const iframeRef = useRef(null); | ||
|
||
useEffect(() => { | ||
const elm = iframeRef.current; | ||
if (!elm) return; | ||
|
||
const listener = (event) => { | ||
try { | ||
const message = JSON.parse(event.data); | ||
switch (message.type) { | ||
case "READY": | ||
setReady(true); | ||
break; | ||
case "SAVE": | ||
onChange(JSON.stringify(message.payload)); | ||
break; | ||
} | ||
} catch (err) { | ||
console.warn("Message cannot be handled by this component"); | ||
} | ||
}; | ||
window.addEventListener("message", listener); | ||
|
||
return () => window.removeEventListener("message", listener); | ||
}, []); | ||
|
||
const postMessage = useCallback( | ||
(message) => { | ||
try { | ||
iframeRef.current.contentWindow.postMessage(JSON.stringify(message), "*"); | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
}, | ||
[iframeRef] | ||
); | ||
|
||
useEffect(() => { | ||
if (isReady && definition) { | ||
postMessage({ type: "LOAD", payload: JSON.parse(definition) }); | ||
} | ||
}, [definition, isReady]); | ||
|
||
return ( | ||
<iframe | ||
ref={iframeRef} | ||
src={`/api/report/reportbro/designer`} | ||
frameBorder="0" | ||
width="100%" | ||
height="100%" | ||
allowFullScreen | ||
{...delegated} | ||
/> | ||
); | ||
}; | ||
|
||
export default ReportBro; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, CircularProgress } from "@material-ui/core"; | ||
import { makeStyles } from "@material-ui/styles"; | ||
import { useTranslations, useModulesManager } from "@openimis/fe-core"; | ||
import ReportBro from "./ReportBro"; | ||
import { useOverrideReportMutation, useReportQuery } from "../hooks"; | ||
|
||
const useDialogStyles = makeStyles(() => ({ | ||
root: { | ||
zIndex: "2001 !important", | ||
}, | ||
paper: { | ||
height: "100%", | ||
}, | ||
})); | ||
|
||
const useDialogContentStyles = makeStyles(() => ({ | ||
dialogContent: { | ||
padding: 0, | ||
}, | ||
})); | ||
|
||
const ReportDefinitionEditorDialog = (props) => { | ||
const { name, onClose } = props; | ||
const modulesManager = useModulesManager(); | ||
const { formatMessage } = useTranslations("tools", modulesManager); | ||
const [resetKey, setResetKey] = useState(null); | ||
const dialogClasses = useDialogStyles(); | ||
const classes = useDialogContentStyles(); | ||
const { report, isLoading } = useReportQuery({ name }); | ||
const { mutate } = useOverrideReportMutation(); | ||
const [definition, setDefinition] = useState(null); | ||
|
||
useEffect(() => { | ||
setDefinition(report?.definition ?? report?.defaultReport); | ||
}, [report]); | ||
|
||
const onReset = () => { | ||
setResetKey(Date.now()); | ||
setDefinition(report?.defaultReport); | ||
}; | ||
|
||
const handleChange = async (value) => { | ||
await mutate({ name, definition: value }); | ||
onClose(); | ||
}; | ||
|
||
return ( | ||
<Dialog classes={dialogClasses} maxWidth="xl" open fullWidth onClose={onClose}> | ||
<DialogTitle>{formatMessage("ReportDefinitionEditor.title")}</DialogTitle> | ||
<DialogContent className={classes.dialogContent}> | ||
{report && <ReportBro key={resetKey} definition={definition} onChange={handleChange} />} | ||
{isLoading && <CircularProgress />} | ||
</DialogContent> | ||
<DialogActions> | ||
{report?.defaultReport && ( | ||
<Button onClick={onReset}>{formatMessage("tools.ReportDefinitionEditor.resetToDefault")}</Button> | ||
)} | ||
<Button onClick={onClose}>{formatMessage("tools.ReportDefinitionEditor.cancel")}</Button> | ||
</DialogActions> | ||
</Dialog> | ||
); | ||
}; | ||
|
||
export default ReportDefinitionEditorDialog; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { Grid } from "@material-ui/core"; | ||
import { makeStyles } from "@material-ui/styles"; | ||
import { Form, PublishedComponent } from "@openimis/fe-core"; | ||
import React from "react"; | ||
|
||
const useStyles = makeStyles((theme) => ({ | ||
item: theme.paper.item, | ||
})); | ||
|
||
const MainPanel = ({ edited, onEditedChanged }) => { | ||
const classes = useStyles(); | ||
return ( | ||
<Grid container> | ||
<Grid item xs={4} className={classes.item}> | ||
<PublishedComponent | ||
pubRef="tools.ReportDefinitionEditor" | ||
value={edited.definition} | ||
defaultValue={edited.defaultReport} | ||
onChange={(definition) => onEditedChanged({ ...edited, definition })} | ||
/> | ||
</Grid> | ||
</Grid> | ||
); | ||
}; | ||
|
||
const ReportForm = ({ onBack, onSave, report, onChange }) => { | ||
if (!report) { | ||
return null; | ||
} | ||
return ( | ||
<Form | ||
module="tools" | ||
title="ReportForm.title" | ||
titleParams={{ name: report.name }} | ||
HeadPanel={MainPanel} | ||
onEditedChanged={onChange} | ||
edited={report} | ||
edited_id={report.name} | ||
save={onSave} | ||
back={onBack} | ||
/> | ||
); | ||
}; | ||
export default ReportForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import React, { useMemo, useState } from "react"; | ||
import { Autocomplete, useModulesManager, useTranslations } from "@openimis/fe-core"; | ||
import { useReportsQuery } from "../hooks"; | ||
|
||
const ReportPicker = (props) => { | ||
const { onChange, readOnly = false, required = false, withLabel = true, value, label, placeholder } = props; | ||
const modulesManager = useModulesManager(); | ||
const { formatMessage } = useTranslations("tools", modulesManager); | ||
const [searchString, setSearchString] = useState(""); | ||
const { isLoading, data } = useReportsQuery(); | ||
|
||
const options = useMemo(() => { | ||
if (searchString) { | ||
return data?.reports.filter((x) => x.name.toLowerCase().includes(searchString.toLowerCase())); | ||
} | ||
return data?.reports; | ||
}, [searchString, data]); | ||
|
||
return ( | ||
<Autocomplete | ||
required={required} | ||
placeholder={placeholder} | ||
label={label ?? formatMessage("ReportPicker.label")} | ||
withLabel={withLabel} | ||
readOnly={readOnly} | ||
options={options ?? []} | ||
isLoading={isLoading} | ||
value={value} | ||
getOptionLabel={(o) => o?.description} | ||
onChange={onChange} | ||
onInputChange={setSearchString} | ||
/> | ||
); | ||
}; | ||
|
||
export default ReportPicker; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React, { useCallback, useState } from "react"; | ||
import { useReportsQuery } from "../hooks"; | ||
import { Searcher, useTranslations, useModulesManager } from "@openimis/fe-core"; | ||
import GenerateReportPicker from "./GenerateReportPicker"; | ||
import { Box, Button } from "@material-ui/core"; | ||
import ReportDefinitionEditorDialog from "./ReportDefinitionEditorDialog"; | ||
|
||
const HEADERS = ["tools.report.description", "tools.report.module", "tools.report.name", ""]; | ||
|
||
const ReportSearcher = () => { | ||
const modulesManager = useModulesManager(); | ||
const { formatMessageWithValues, formatMessage } = useTranslations("tools", modulesManager); | ||
const { data, isLoading, error, refetch } = useReportsQuery(); | ||
const [editedReport, setEditedReport] = useState(); | ||
const itemFormatters = useCallback( | ||
() => [ | ||
(r) => r.description, | ||
(r) => r.module, | ||
(r) => r.name, | ||
(r) => ( | ||
<Box display="flex" justifyContent={"flex-end"} gridGap={12}> | ||
<Button onClick={() => setEditedReport(r)} size="small"> | ||
{formatMessage("ReportSearcher.editBtn")} | ||
</Button> | ||
<GenerateReportPicker name={r.name} /> | ||
</Box> | ||
), | ||
], | ||
[] | ||
); | ||
|
||
return ( | ||
<> | ||
<Searcher | ||
module="report" | ||
tableTitle={formatMessageWithValues("ReportSearcher.tableTitle", { count: data?.reports?.length })} | ||
items={data?.reports ?? []} | ||
fetchingItems={isLoading} | ||
errorItems={error} | ||
fetch={() => refetch()} | ||
itemsPageInfo={{ totalCount: data?.reports?.length ?? 0 }} | ||
headers={() => HEADERS} | ||
itemFormatters={itemFormatters} | ||
withPagination={false} | ||
/> | ||
{editedReport && <ReportDefinitionEditorDialog name={editedReport.name} onClose={() => setEditedReport(null)} />} | ||
</> | ||
); | ||
}; | ||
|
||
export default ReportSearcher; |
Oops, something went wrong.