From becd02153c9cecb45041ab7e0b05b8a8cfbcb08a Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 7 Aug 2024 14:15:20 +0200 Subject: [PATCH] feat(dashboard): add sigits filtering, sorting, searching --- src/components/DisplaySigit/index.tsx | 16 ++- src/components/Select/index.tsx | 4 +- src/hooks/useSigitMeta.tsx | 137 +++++++++++-------- src/pages/home/index.tsx | 183 ++++++++++++++------------ src/pages/home/style.module.scss | 8 +- 5 files changed, 203 insertions(+), 145 deletions(-) diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx index a4a7418..e930b37 100644 --- a/src/components/DisplaySigit/index.tsx +++ b/src/components/DisplaySigit/index.tsx @@ -1,10 +1,10 @@ import { Dispatch, SetStateAction, useEffect } from 'react' import { Meta, ProfileMetadata } from '../../types' -import { SignedStatus, useSigitMeta } from '../../hooks/useSigitMeta' +import { SigitInfo, SignedStatus } from '../../hooks/useSigitMeta' import { Event, kinds } from 'nostr-tools' import { Link } from 'react-router-dom' import { MetadataController } from '../../controllers' -import { hexToNpub, npubToHex, shorten } from '../../utils' +import { formatTimestamp, hexToNpub, npubToHex, shorten } from '../../utils' import { appPublicRoutes, appPrivateRoutes } from '../../routes' import { Button, Divider, Tooltip } from '@mui/material' import { DisplaySigner } from '../DisplaySigner' @@ -25,11 +25,17 @@ import { getExtensionIconLabel } from '../getExtensionIconLabel' type SigitProps = { meta: Meta + parsedMeta: SigitInfo profiles: { [key: string]: ProfileMetadata } setProfiles: Dispatch> } -export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => { +export const DisplaySigit = ({ + meta, + parsedMeta, + profiles, + setProfiles +}: SigitProps) => { const { title, createdAt, @@ -37,7 +43,7 @@ export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => { signers, signedStatus, fileExtensions - } = useSigitMeta(meta) + } = parsedMeta useEffect(() => { const hexKeys: string[] = [] @@ -144,7 +150,7 @@ export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => {
- {createdAt} + {createdAt ? formatTimestamp(createdAt) : null}
diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx index c5c39e8..9901fa1 100644 --- a/src/components/Select/index.tsx +++ b/src/components/Select/index.tsx @@ -60,6 +60,7 @@ interface SelectItemProps { } interface SelectProps { + value: T setValue: React.Dispatch> options: SelectItemProps[] name?: string @@ -67,6 +68,7 @@ interface SelectProps { } export function Select({ + value, setValue, options, name, @@ -83,7 +85,7 @@ export function Select({ name={name} size="small" variant="outlined" - defaultValue={options[0].value as string} + value={value} onChange={handleChange} MenuProps={{ MenuListProps: { diff --git a/src/hooks/useSigitMeta.tsx b/src/hooks/useSigitMeta.tsx index b87f3b3..e798ef0 100644 --- a/src/hooks/useSigitMeta.tsx +++ b/src/hooks/useSigitMeta.tsx @@ -1,78 +1,109 @@ import { useEffect, useState } from 'react' import { toast } from 'react-toastify' import { CreateSignatureEventContent, Meta } from '../types' -import { parseJson, formatTimestamp } from '../utils' +import { parseJson } from '../utils' import { Event } from 'nostr-tools' +type npub = `npub1${string}` + export enum SignedStatus { Partial = 'In-Progress', Complete = 'Completed' } +export interface SigitInfo { + createdAt?: number + title?: string + submittedBy?: string + signers: npub[] + fileExtensions: string[] + signedStatus: SignedStatus +} + +export const extractSigitInfo = async (meta: Meta) => { + if (!meta?.createSignature) return + + const sigitInfo: SigitInfo = { + signers: [], + fileExtensions: [], + signedStatus: SignedStatus.Partial + } + + const createSignatureEvent = await parseJson( + meta.createSignature + ).catch((err) => { + console.log('err in parsing the createSignature event:>> ', err) + toast.error( + err.message || 'error occurred in parsing the create signature event' + ) + return + }) + + if (!createSignatureEvent) return + + // created_at in nostr events are stored in seconds + sigitInfo.createdAt = createSignatureEvent.created_at * 1000 + + const createSignatureContent = await parseJson( + createSignatureEvent.content + ).catch((err) => { + console.log(`err in parsing the createSignature event's content :>> `, err) + return + }) + + if (!createSignatureContent) return + + const files = Object.keys(createSignatureContent.fileHashes) + const extensions = files.reduce((result: string[], file: string) => { + const extension = file.split('.').pop() + if (extension) { + result.push(extension) + } + return result + }, []) + + const signedBy = Object.keys(meta.docSignatures) as npub[] + const isCompletelySigned = createSignatureContent.signers.every((signer) => + signedBy.includes(signer) + ) + + sigitInfo.title = createSignatureContent.title + sigitInfo.submittedBy = createSignatureEvent.pubkey + sigitInfo.signers = createSignatureContent.signers + sigitInfo.fileExtensions = extensions + + if (isCompletelySigned) { + sigitInfo.signedStatus = SignedStatus.Complete + } + + return sigitInfo +} + export const useSigitMeta = (meta: Meta) => { const [title, setTitle] = useState() - const [createdAt, setCreatedAt] = useState('') + const [createdAt, setCreatedAt] = useState() const [submittedBy, setSubmittedBy] = useState() - const [signers, setSigners] = useState<`npub1${string}`[]>([]) + const [signers, setSigners] = useState([]) const [signedStatus, setSignedStatus] = useState( SignedStatus.Partial ) const [fileExtensions, setFileExtensions] = useState([]) useEffect(() => { - const extractInfo = async () => { - const createSignatureEvent = await parseJson( - meta.createSignature - ).catch((err) => { - console.log('err in parsing the createSignature event:>> ', err) - toast.error( - err.message || 'error occurred in parsing the create signature event' - ) - return null - }) + const getSigitInfo = async () => { + const sigitInfo = await extractSigitInfo(meta) - if (!createSignatureEvent) return + if (!sigitInfo) return - // created_at in nostr events are stored in seconds - // convert it to ms before formatting - setCreatedAt(formatTimestamp(createSignatureEvent.created_at * 1000)) - - const createSignatureContent = - await parseJson( - createSignatureEvent.content - ).catch((err) => { - console.log( - `err in parsing the createSignature event's content :>> `, - err - ) - return null - }) - - if (!createSignatureContent) return - - const files = Object.keys(createSignatureContent.fileHashes) - const extensions = files.reduce((result: string[], file: string) => { - const extension = file.split('.').pop() - if (extension) { - result.push(extension) - } - return result - }, []) - - setTitle(createSignatureContent.title) - setSubmittedBy(createSignatureEvent.pubkey) - setSigners(createSignatureContent.signers) - setFileExtensions(extensions) - - const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[] - const isCompletelySigned = createSignatureContent.signers.every( - (signer) => signedBy.includes(signer) - ) - if (isCompletelySigned) { - setSignedStatus(SignedStatus.Complete) - } + setTitle(sigitInfo.title) + setCreatedAt(sigitInfo.createdAt) + setSubmittedBy(sigitInfo.submittedBy) + setSigners(sigitInfo.signers) + setSignedStatus(sigitInfo.signedStatus) + setFileExtensions(sigitInfo.fileExtensions) } - extractInfo() + + getSigitInfo() }, [meta]) return { diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 37951f0..d728a78 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -1,4 +1,4 @@ -import { Button, Divider, TextField, Tooltip } from '@mui/material' +import { Button, TextField } from '@mui/material' import JSZip from 'jszip' import { useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' @@ -7,25 +7,25 @@ import { useAppSelector } from '../../hooks' import { appPrivateRoutes, appPublicRoutes } from '../../routes' import { Meta, ProfileMetadata } from '../../types' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { - faAdd, - faFilter, - faFilterCircleXmark, - faSearch -} from '@fortawesome/free-solid-svg-icons' +import { faSearch } from '@fortawesome/free-solid-svg-icons' import { Select } from '../../components/Select' import { DisplaySigit } from '../../components/DisplaySigit' import { Container } from '../../components/Container' import styles from './style.module.scss' +import { + extractSigitInfo, + SigitInfo, + SignedStatus +} from '../../hooks/useSigitMeta' // Unsupported Filter options are commented const FILTERS = [ 'Show all', // 'Drafts', 'In-progress', - 'Completed' - // 'Archived' + 'Completed', + 'Archived' ] as const type Filter = (typeof FILTERS)[number] @@ -41,7 +41,11 @@ type Sort = (typeof SORT_BY)[number]['value'] export const HomePage = () => { const navigate = useNavigate() const fileInputRef = useRef(null) - const [sigits, setSigits] = useState([]) + + const [sigits, setSigits] = useState<{ [key: string]: Meta }>({}) + const [parsedSigits, setParsedSigits] = useState<{ + [key: string]: SigitInfo + }>({}) const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>( {} ) @@ -49,7 +53,24 @@ export const HomePage = () => { useEffect(() => { if (usersAppData) { - setSigits(Object.values(usersAppData.sigits)) + const getSigitInfo = async () => { + for (const key in usersAppData.sigits) { + if (Object.prototype.hasOwnProperty.call(usersAppData.sigits, key)) { + const sigitInfo = await extractSigitInfo(usersAppData.sigits[key]) + if (sigitInfo) { + setParsedSigits((prev) => { + return { + ...prev, + [key]: sigitInfo + } + }) + } + } + } + } + + setSigits(usersAppData.sigits) + getSigitInfo() } }, [usersAppData]) @@ -99,42 +120,46 @@ export const HomePage = () => { } } + const [search, setSearch] = useState('') const [filter, setFilter] = useState('Show all') - const [isFilterVisible, setIsFilterVisible] = useState(true) - const [sort, setSort] = useState('asc') + const [sort, setSort] = useState('desc') return (
- {isFilterVisible && ( - <> - { - return { ...s } - })} - /> - - )} +
+ { + return { ...s } + })} + /> +
{ + setSearch(e.currentTarget.value) + }} size="small" sx={{ + width: '100%', fontSize: '16px', - height: '34px', borderTopLeftRadius: 'var(----mui-shape-borderRadius)', borderBottomLeftRadius: 'var(----mui-shape-borderRadius)', '& .MuiInputBase-root': { @@ -142,7 +167,7 @@ export const HomePage = () => { borderBottomRightRadius: 0 }, '& .MuiInputBase-input': { - padding: '5.5px 14px' + padding: '7px 14px' }, '& .MuiOutlinedInput-notchedOutline': { display: 'none' @@ -152,7 +177,7 @@ export const HomePage = () => {
- - - - - - - -
-
+
+
Click or drag files to upload!
- {sigits.map((sigit, index) => ( - - ))} + {Object.keys(parsedSigits) + .filter((s) => { + const { title, signedStatus } = parsedSigits[s] + const isMatch = title?.toLowerCase().includes(search.toLowerCase()) + switch (filter) { + case 'Completed': + return signedStatus === SignedStatus.Complete && isMatch + case 'In-progress': + return signedStatus === SignedStatus.Partial && isMatch + case 'Show all': + return isMatch + default: + console.error('Filter case not handled.') + } + }) + .sort((a, b) => { + const x = parsedSigits[a].createdAt ?? 0 + const y = parsedSigits[b].createdAt ?? 0 + return sort === 'desc' ? y - x : x - y + }) + .map((key) => ( + + ))}
) diff --git a/src/pages/home/style.module.scss b/src/pages/home/style.module.scss index bd0dc08..9a1b4c0 100644 --- a/src/pages/home/style.module.scss +++ b/src/pages/home/style.module.scss @@ -10,13 +10,17 @@ .header { display: flex; gap: 10px; - align-items: center; @container (width < 610px) { - flex-wrap: wrap; + flex-direction: column-reverse; } } +.filters { + display: flex; + gap: 10px; +} + .actionButtons { display: flex; justify-content: end;