mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-19 07:55:25 +02:00
improved ReportsList.tsx
This commit is contained in:
parent
c7381ed5c6
commit
044c98f348
@ -31,6 +31,7 @@
|
||||
"react-router-dom": "^6.4.2",
|
||||
"react-use": "^17.4.0",
|
||||
"secure-random-password": "^0.2.3",
|
||||
"sort-array": "^4.1.5",
|
||||
"ua-parser-js": "^1.0.2",
|
||||
"use-system-theme": "^0.1.1",
|
||||
"yup": "^0.32.11"
|
||||
@ -46,6 +47,7 @@
|
||||
"@types/react-router": "^5.1.19",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/secure-random-password": "^0.2.1",
|
||||
"@types/sort-array": "^4.1.0",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/yup": "^0.32.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
||||
|
@ -2,31 +2,63 @@ import {ReactElement, useContext} from "react"
|
||||
import {useAsync} from "react-use"
|
||||
import camelcaseKeys from "camelcase-keys"
|
||||
|
||||
import {DecryptedReportContent} from "~/server-types"
|
||||
import {DecryptedReportContent, Report} from "~/server-types"
|
||||
import AuthContext from "~/AuthContext/AuthContext"
|
||||
import parseDecryptedReport from "~/apis/helpers/parse-decrypted-report"
|
||||
|
||||
export interface DecryptReportProps {
|
||||
interface DecryptReportPropsBase {
|
||||
encryptedContent?: string
|
||||
reports?: Report[]
|
||||
children: (
|
||||
report: DecryptedReportContent | DecryptedReportContent[],
|
||||
) => ReactElement
|
||||
}
|
||||
|
||||
interface DecryptReportPropsEncryptedContent {
|
||||
encryptedContent: string
|
||||
children: (report: DecryptedReportContent) => ReactElement
|
||||
}
|
||||
|
||||
interface DecryptReportPropsReports {
|
||||
reports: Report[]
|
||||
children: (reports: DecryptedReportContent[]) => ReactElement
|
||||
}
|
||||
|
||||
export type DecryptReportProps = DecryptReportPropsBase &
|
||||
(DecryptReportPropsEncryptedContent | DecryptReportPropsReports)
|
||||
|
||||
export default function DecryptReport({
|
||||
encryptedContent,
|
||||
reports,
|
||||
children: render,
|
||||
}: DecryptReportProps): ReactElement {
|
||||
const {_decryptUsingPrivateKey} = useContext(AuthContext)
|
||||
|
||||
const {value} = useAsync(async () => {
|
||||
const message = await _decryptUsingPrivateKey(encryptedContent)
|
||||
const content = camelcaseKeys(JSON.parse(message), {deep: true})
|
||||
const decrypt = async (
|
||||
content: string,
|
||||
): Promise<DecryptedReportContent> =>
|
||||
parseDecryptedReport(
|
||||
camelcaseKeys(
|
||||
JSON.parse(await _decryptUsingPrivateKey(content)),
|
||||
{deep: true},
|
||||
),
|
||||
)
|
||||
|
||||
return parseDecryptedReport(content)
|
||||
}, [encryptedContent])
|
||||
if (encryptedContent) {
|
||||
return decrypt(encryptedContent)
|
||||
} else {
|
||||
return await Promise.all(
|
||||
reports!.map(report => decrypt(report.encryptedContent)),
|
||||
)
|
||||
}
|
||||
}, [encryptedContent, reports])
|
||||
|
||||
if (!value) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
console.log(value)
|
||||
|
||||
return render(value)
|
||||
}
|
||||
|
@ -12,8 +12,6 @@ export default function WithEncryptionRequired(
|
||||
return (props: any): ReactElement => {
|
||||
const user = useUser()
|
||||
|
||||
console.log("withencryption required", user)
|
||||
|
||||
if (!user.encryptedPassword) {
|
||||
return (
|
||||
<Grid container spacing={4}>
|
||||
|
@ -34,10 +34,10 @@ const SECTION_TEXT_MAP: Record<NavigationSection, string> = {
|
||||
}
|
||||
|
||||
const PATH_SECTION_MAP: Record<string, NavigationSection> = {
|
||||
"/": NavigationSection.Overview,
|
||||
"/aliases": NavigationSection.Aliases,
|
||||
"/reports": NavigationSection.Reports,
|
||||
"/settings": NavigationSection.Settings,
|
||||
"": NavigationSection.Overview,
|
||||
aliases: NavigationSection.Aliases,
|
||||
reports: NavigationSection.Reports,
|
||||
settings: NavigationSection.Settings,
|
||||
}
|
||||
|
||||
export default function NavigationButton({
|
||||
@ -45,7 +45,7 @@ export default function NavigationButton({
|
||||
}: NavigationButtonProps): ReactElement {
|
||||
const location = useLocation()
|
||||
|
||||
const currentSection = PATH_SECTION_MAP[location.pathname]
|
||||
const currentSection = PATH_SECTION_MAP[location.pathname.split("/")[1]]
|
||||
const Icon = SECTION_ICON_MAP[section]
|
||||
const text = SECTION_TEXT_MAP[section]
|
||||
|
||||
|
123
src/route-widgets/ReportsRoute/ReportsList.tsx
Normal file
123
src/route-widgets/ReportsRoute/ReportsList.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import {ReactElement, useState} from "react"
|
||||
import {MdList} from "react-icons/md"
|
||||
import {FaMask} from "react-icons/fa"
|
||||
import groupArray from "group-array"
|
||||
import sortArray from "sort-array"
|
||||
|
||||
import {
|
||||
Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material"
|
||||
|
||||
import {DecryptedReportContent} from "~/server-types"
|
||||
|
||||
import ReportInformationItem from "./ReportInformationItem"
|
||||
|
||||
export interface ReportsListProps {
|
||||
reports: DecryptedReportContent[]
|
||||
}
|
||||
|
||||
enum SortingView {
|
||||
List = "List",
|
||||
GroupByAlias = "GroupByAlias",
|
||||
}
|
||||
|
||||
const SORTING_VIEW_NAME_MAP: Record<SortingView, string> = {
|
||||
[SortingView.List]: "List reports by their date",
|
||||
[SortingView.GroupByAlias]: "Group reports by their aliases",
|
||||
}
|
||||
|
||||
const SORTING_VIEW_ICON_MAP: Record<SortingView, ReactElement> = {
|
||||
[SortingView.List]: <MdList />,
|
||||
[SortingView.GroupByAlias]: <FaMask />,
|
||||
}
|
||||
|
||||
export default function ReportsList({reports}: ReportsListProps): ReactElement {
|
||||
const [sortingView, setSortingView] = useState<SortingView>(
|
||||
SortingView.List,
|
||||
)
|
||||
|
||||
return (
|
||||
<Grid direction="column" container spacing={4}>
|
||||
<Grid item>
|
||||
<Typography variant="h6" component="h2">
|
||||
Reports
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<TextField
|
||||
value={sortingView}
|
||||
onChange={event =>
|
||||
setSortingView(event.target.value as SortingView)
|
||||
}
|
||||
label="Sorting"
|
||||
id="sorting"
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
{SORTING_VIEW_ICON_MAP[sortingView]}
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
select
|
||||
>
|
||||
{Object.keys(SORTING_VIEW_NAME_MAP).map(name => (
|
||||
<MenuItem key={name} value={name}>
|
||||
{SORTING_VIEW_NAME_MAP[name as SortingView]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
{(() => {
|
||||
switch (sortingView) {
|
||||
case SortingView.List:
|
||||
return sortArray(
|
||||
reports as DecryptedReportContent[],
|
||||
{
|
||||
by: "messageDetails.meta.createdAt",
|
||||
order: "desc",
|
||||
},
|
||||
).map(report => (
|
||||
<ReportInformationItem
|
||||
report={report}
|
||||
key={report.id}
|
||||
/>
|
||||
))
|
||||
|
||||
case SortingView.GroupByAlias:
|
||||
return Object.entries(
|
||||
groupArray(
|
||||
reports as DecryptedReportContent[],
|
||||
"messageDetails.meta.to",
|
||||
),
|
||||
).map(
|
||||
([alias, reports]: [
|
||||
string,
|
||||
DecryptedReportContent[],
|
||||
]) => (
|
||||
<>
|
||||
<Typography
|
||||
variant="caption"
|
||||
component="h2"
|
||||
>
|
||||
{alias}
|
||||
</Typography>
|
||||
{reports.map(report => (
|
||||
<ReportInformationItem
|
||||
report={report}
|
||||
key={report.id}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
),
|
||||
)
|
||||
}
|
||||
})()}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
@ -56,7 +56,7 @@ export default function AuthenticatedRoute(): ReactElement {
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={8} md={10}>
|
||||
<Paper>
|
||||
<Box padding={4} maxHeight="60vh" overflow="scroll">
|
||||
<Box padding={4}>
|
||||
<Outlet />
|
||||
</Box>
|
||||
</Paper>
|
||||
|
@ -27,48 +27,71 @@ export default function ReportDetailRoute(): ReactElement {
|
||||
>
|
||||
{report => (
|
||||
<Grid container spacing={4}>
|
||||
<Grid item>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4" component="h1">
|
||||
Email Report
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6" component="h2">
|
||||
Email information
|
||||
</Typography>
|
||||
<Box component="dl">
|
||||
<Typography
|
||||
variant="overline"
|
||||
component="dt"
|
||||
>
|
||||
From
|
||||
</Typography>
|
||||
<Typography variant="body1" component="dd">
|
||||
{report.messageDetails.meta.from}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box component="dl">
|
||||
<Typography
|
||||
variant="overline"
|
||||
component="dt"
|
||||
>
|
||||
To
|
||||
</Typography>
|
||||
<Typography variant="body1" component="dd">
|
||||
{report.messageDetails.meta.to}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box component="dl">
|
||||
<Typography
|
||||
variant="overline"
|
||||
component="dt"
|
||||
>
|
||||
Subject
|
||||
</Typography>
|
||||
<Typography variant="body1" component="dd">
|
||||
{report.messageDetails.content.subject}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Grid container columnSpacing={4}>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Box component="dl">
|
||||
<Typography
|
||||
variant="overline"
|
||||
component="dt"
|
||||
>
|
||||
From
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
component="dd"
|
||||
>
|
||||
{
|
||||
report.messageDetails.meta
|
||||
.from
|
||||
}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Box component="dl">
|
||||
<Typography
|
||||
variant="overline"
|
||||
component="dt"
|
||||
>
|
||||
To
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
component="dd"
|
||||
>
|
||||
{report.messageDetails.meta.to}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<Box component="dl">
|
||||
<Typography
|
||||
variant="overline"
|
||||
component="dt"
|
||||
>
|
||||
Subject
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
component="dd"
|
||||
>
|
||||
{
|
||||
report.messageDetails
|
||||
.content.subject
|
||||
}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="h6" component="h2">
|
||||
|
@ -4,12 +4,12 @@ import {AxiosError} from "axios"
|
||||
import {useQuery} from "@tanstack/react-query"
|
||||
import {List} from "@mui/material"
|
||||
|
||||
import {PaginationResult, Report} from "~/server-types"
|
||||
import {DecryptedReportContent, PaginationResult, Report} from "~/server-types"
|
||||
import {getReports} from "~/apis"
|
||||
import {WithEncryptionRequired} from "~/hocs"
|
||||
import {DecryptReport} from "~/components"
|
||||
import QueryResult from "~/components/QueryResult"
|
||||
import ReportInformationItem from "~/route-widgets/ReportsRoute/ReportInformationItem"
|
||||
import ReportsList from "~/route-widgets/ReportsRoute/ReportsList"
|
||||
|
||||
function ReportsRoute(): ReactElement {
|
||||
const query = useQuery<PaginationResult<Report>, AxiosError>(
|
||||
@ -21,19 +21,13 @@ function ReportsRoute(): ReactElement {
|
||||
<QueryResult<PaginationResult<Report>> query={query}>
|
||||
{result => (
|
||||
<List>
|
||||
{result.items.map(report => (
|
||||
<DecryptReport
|
||||
key={report.id}
|
||||
encryptedContent={report.encryptedContent}
|
||||
>
|
||||
{report => (
|
||||
<ReportInformationItem
|
||||
report={report}
|
||||
key={report.id}
|
||||
/>
|
||||
)}
|
||||
</DecryptReport>
|
||||
))}
|
||||
<DecryptReport reports={result.items}>
|
||||
{reports => (
|
||||
<ReportsList
|
||||
reports={reports as DecryptedReportContent[]}
|
||||
/>
|
||||
)}
|
||||
</DecryptReport>
|
||||
</List>
|
||||
)}
|
||||
</QueryResult>
|
||||
|
Loading…
x
Reference in New Issue
Block a user