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-router-dom": "^6.4.2",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"secure-random-password": "^0.2.3",
|
"secure-random-password": "^0.2.3",
|
||||||
|
"sort-array": "^4.1.5",
|
||||||
"ua-parser-js": "^1.0.2",
|
"ua-parser-js": "^1.0.2",
|
||||||
"use-system-theme": "^0.1.1",
|
"use-system-theme": "^0.1.1",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
@ -46,6 +47,7 @@
|
|||||||
"@types/react-router": "^5.1.19",
|
"@types/react-router": "^5.1.19",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/secure-random-password": "^0.2.1",
|
"@types/secure-random-password": "^0.2.1",
|
||||||
|
"@types/sort-array": "^4.1.0",
|
||||||
"@types/ua-parser-js": "^0.7.36",
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
"@types/yup": "^0.32.0",
|
"@types/yup": "^0.32.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
||||||
|
@ -2,31 +2,63 @@ import {ReactElement, useContext} from "react"
|
|||||||
import {useAsync} from "react-use"
|
import {useAsync} from "react-use"
|
||||||
import camelcaseKeys from "camelcase-keys"
|
import camelcaseKeys from "camelcase-keys"
|
||||||
|
|
||||||
import {DecryptedReportContent} from "~/server-types"
|
import {DecryptedReportContent, Report} from "~/server-types"
|
||||||
import AuthContext from "~/AuthContext/AuthContext"
|
import AuthContext from "~/AuthContext/AuthContext"
|
||||||
import parseDecryptedReport from "~/apis/helpers/parse-decrypted-report"
|
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
|
encryptedContent: string
|
||||||
children: (report: DecryptedReportContent) => ReactElement
|
children: (report: DecryptedReportContent) => ReactElement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DecryptReportPropsReports {
|
||||||
|
reports: Report[]
|
||||||
|
children: (reports: DecryptedReportContent[]) => ReactElement
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DecryptReportProps = DecryptReportPropsBase &
|
||||||
|
(DecryptReportPropsEncryptedContent | DecryptReportPropsReports)
|
||||||
|
|
||||||
export default function DecryptReport({
|
export default function DecryptReport({
|
||||||
encryptedContent,
|
encryptedContent,
|
||||||
|
reports,
|
||||||
children: render,
|
children: render,
|
||||||
}: DecryptReportProps): ReactElement {
|
}: DecryptReportProps): ReactElement {
|
||||||
const {_decryptUsingPrivateKey} = useContext(AuthContext)
|
const {_decryptUsingPrivateKey} = useContext(AuthContext)
|
||||||
|
|
||||||
const {value} = useAsync(async () => {
|
const {value} = useAsync(async () => {
|
||||||
const message = await _decryptUsingPrivateKey(encryptedContent)
|
const decrypt = async (
|
||||||
const content = camelcaseKeys(JSON.parse(message), {deep: true})
|
content: string,
|
||||||
|
): Promise<DecryptedReportContent> =>
|
||||||
|
parseDecryptedReport(
|
||||||
|
camelcaseKeys(
|
||||||
|
JSON.parse(await _decryptUsingPrivateKey(content)),
|
||||||
|
{deep: true},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return parseDecryptedReport(content)
|
if (encryptedContent) {
|
||||||
}, [encryptedContent])
|
return decrypt(encryptedContent)
|
||||||
|
} else {
|
||||||
|
return await Promise.all(
|
||||||
|
reports!.map(report => decrypt(report.encryptedContent)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [encryptedContent, reports])
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(value)
|
||||||
|
|
||||||
return render(value)
|
return render(value)
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,6 @@ export default function WithEncryptionRequired(
|
|||||||
return (props: any): ReactElement => {
|
return (props: any): ReactElement => {
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
console.log("withencryption required", user)
|
|
||||||
|
|
||||||
if (!user.encryptedPassword) {
|
if (!user.encryptedPassword) {
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={4}>
|
<Grid container spacing={4}>
|
||||||
|
@ -34,10 +34,10 @@ const SECTION_TEXT_MAP: Record<NavigationSection, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PATH_SECTION_MAP: Record<string, NavigationSection> = {
|
const PATH_SECTION_MAP: Record<string, NavigationSection> = {
|
||||||
"/": NavigationSection.Overview,
|
"": NavigationSection.Overview,
|
||||||
"/aliases": NavigationSection.Aliases,
|
aliases: NavigationSection.Aliases,
|
||||||
"/reports": NavigationSection.Reports,
|
reports: NavigationSection.Reports,
|
||||||
"/settings": NavigationSection.Settings,
|
settings: NavigationSection.Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NavigationButton({
|
export default function NavigationButton({
|
||||||
@ -45,7 +45,7 @@ export default function NavigationButton({
|
|||||||
}: NavigationButtonProps): ReactElement {
|
}: NavigationButtonProps): ReactElement {
|
||||||
const location = useLocation()
|
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 Icon = SECTION_ICON_MAP[section]
|
||||||
const text = SECTION_TEXT_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>
|
||||||
<Grid item xs={12} sm={8} md={10}>
|
<Grid item xs={12} sm={8} md={10}>
|
||||||
<Paper>
|
<Paper>
|
||||||
<Box padding={4} maxHeight="60vh" overflow="scroll">
|
<Box padding={4}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
@ -27,48 +27,71 @@ export default function ReportDetailRoute(): ReactElement {
|
|||||||
>
|
>
|
||||||
{report => (
|
{report => (
|
||||||
<Grid container spacing={4}>
|
<Grid container spacing={4}>
|
||||||
<Grid item>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h4" component="h1">
|
<Typography variant="h4" component="h1">
|
||||||
Email Report
|
Email Report
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h6" component="h2">
|
<Typography variant="h6" component="h2">
|
||||||
Email information
|
Email information
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box component="dl">
|
<Grid container columnSpacing={4}>
|
||||||
<Typography
|
<Grid item xs={12} md={6} lg={4}>
|
||||||
variant="overline"
|
<Box component="dl">
|
||||||
component="dt"
|
<Typography
|
||||||
>
|
variant="overline"
|
||||||
From
|
component="dt"
|
||||||
</Typography>
|
>
|
||||||
<Typography variant="body1" component="dd">
|
From
|
||||||
{report.messageDetails.meta.from}
|
</Typography>
|
||||||
</Typography>
|
<Typography
|
||||||
</Box>
|
variant="body1"
|
||||||
<Box component="dl">
|
component="dd"
|
||||||
<Typography
|
>
|
||||||
variant="overline"
|
{
|
||||||
component="dt"
|
report.messageDetails.meta
|
||||||
>
|
.from
|
||||||
To
|
}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" component="dd">
|
</Box>
|
||||||
{report.messageDetails.meta.to}
|
</Grid>
|
||||||
</Typography>
|
<Grid item xs={12} md={6} lg={4}>
|
||||||
</Box>
|
<Box component="dl">
|
||||||
<Box component="dl">
|
<Typography
|
||||||
<Typography
|
variant="overline"
|
||||||
variant="overline"
|
component="dt"
|
||||||
component="dt"
|
>
|
||||||
>
|
To
|
||||||
Subject
|
</Typography>
|
||||||
</Typography>
|
<Typography
|
||||||
<Typography variant="body1" component="dd">
|
variant="body1"
|
||||||
{report.messageDetails.content.subject}
|
component="dd"
|
||||||
</Typography>
|
>
|
||||||
</Box>
|
{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>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography variant="h6" component="h2">
|
<Typography variant="h6" component="h2">
|
||||||
|
@ -4,12 +4,12 @@ import {AxiosError} from "axios"
|
|||||||
import {useQuery} from "@tanstack/react-query"
|
import {useQuery} from "@tanstack/react-query"
|
||||||
import {List} from "@mui/material"
|
import {List} from "@mui/material"
|
||||||
|
|
||||||
import {PaginationResult, Report} from "~/server-types"
|
import {DecryptedReportContent, PaginationResult, Report} from "~/server-types"
|
||||||
import {getReports} from "~/apis"
|
import {getReports} from "~/apis"
|
||||||
import {WithEncryptionRequired} from "~/hocs"
|
import {WithEncryptionRequired} from "~/hocs"
|
||||||
import {DecryptReport} from "~/components"
|
import {DecryptReport} from "~/components"
|
||||||
import QueryResult from "~/components/QueryResult"
|
import QueryResult from "~/components/QueryResult"
|
||||||
import ReportInformationItem from "~/route-widgets/ReportsRoute/ReportInformationItem"
|
import ReportsList from "~/route-widgets/ReportsRoute/ReportsList"
|
||||||
|
|
||||||
function ReportsRoute(): ReactElement {
|
function ReportsRoute(): ReactElement {
|
||||||
const query = useQuery<PaginationResult<Report>, AxiosError>(
|
const query = useQuery<PaginationResult<Report>, AxiosError>(
|
||||||
@ -21,19 +21,13 @@ function ReportsRoute(): ReactElement {
|
|||||||
<QueryResult<PaginationResult<Report>> query={query}>
|
<QueryResult<PaginationResult<Report>> query={query}>
|
||||||
{result => (
|
{result => (
|
||||||
<List>
|
<List>
|
||||||
{result.items.map(report => (
|
<DecryptReport reports={result.items}>
|
||||||
<DecryptReport
|
{reports => (
|
||||||
key={report.id}
|
<ReportsList
|
||||||
encryptedContent={report.encryptedContent}
|
reports={reports as DecryptedReportContent[]}
|
||||||
>
|
/>
|
||||||
{report => (
|
)}
|
||||||
<ReportInformationItem
|
</DecryptReport>
|
||||||
report={report}
|
|
||||||
key={report.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</DecryptReport>
|
|
||||||
))}
|
|
||||||
</List>
|
</List>
|
||||||
)}
|
)}
|
||||||
</QueryResult>
|
</QueryResult>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user