Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release Merge to Production #240

Merged
merged 8 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions public/print.css
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ h1 + h3 {
}

.button-container-right {
float: right;
padding-left: 0.75rem;
padding-right: 0.75rem;
padding-right: 0.75rem;
float: right;
}
.button-container-center {
float: center;
Expand Down Expand Up @@ -401,4 +401,8 @@ btn-light:hover {
position: relative; /* Position for absolute child */
height: auto; /* Allow height to adjust based on content */
max-height: 500px; /* Maximum height */
}

.align-right{
text-align: right;
}
8 changes: 6 additions & 2 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ h1 + h3 {
}

.button-container-right {
float: right;
padding-left: 0.75rem;
padding-right: 0.75rem;
padding-right: 0.75rem;
float: right;
}
.button-container-center {
float: center;
Expand Down Expand Up @@ -401,4 +401,8 @@ btn-light:hover {
position: relative; /* Position for absolute child */
height: auto; /* Allow height to adjust based on content */
max-height: 500px; /* Maximum height */
}

.align-right{
text-align: right;
}
60 changes: 60 additions & 0 deletions src/components/export_document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react'
import JSONValue from '../types/json_value.type'
import { Button } from 'react-bootstrap'
import { TfiImport } from 'react-icons/tfi'
import { EXPORT_FILE_TYPE } from '../utilities/paths_utils'

interface ExportDocProps {
sendData: JSONValue // The data to be downloaded
fileName: string // The name of the file
}

/**
* A component that triggers the download of a document as a file.
* @param sendData - The data to be exported, json data from the DB.
* @param fileName - The name of the file to be downloaded. (extension as *.qit)
* @returns A React element that renders a button for downloading the document.
*/
const ExportDoc: React.FC<ExportDocProps> = ({
sendData,
fileName,
}: any): JSX.Element => {
const handleDownload = () => {
try {
//Create a Blob from the sendData
const blob = new Blob([sendData as BlobPart], {
type: 'application/json',
})
const url = URL.createObjectURL(blob)

// Create a temporary anchor element to trigger the download
const link = document.createElement('a')
link.href = url
link.setAttribute('download', `${fileName + EXPORT_FILE_TYPE}`)

// Append to the body, click and remove it
document.body.appendChild(link)
link.click()
document.body.removeChild(link)

// Clean up the URL object
URL.revokeObjectURL(url)
} catch (error) {
console.log('Error in exporting the document', error)
}
}
return (
<Button
variant="light"
onClick={event => {
event.stopPropagation()
event.preventDefault()
handleDownload()
}}
>
<TfiImport size={20} />
</Button>
)
}

export default ExportDoc
40 changes: 40 additions & 0 deletions src/components/export_document_wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { FC, useEffect, useState } from 'react'
import ExportDoc from './export_document'
import { exportDocumentAsJSONObject, useDB } from '../utilities/database_utils'
import JSONValue from '../types/json_value.type'

// Define the props interface for ExportDocWrapper
interface ExportDocWrapperProps {
docId: string
docName: string
includeChild: boolean
}

/**
* A wrapper component for `ExportDoc` that exports a document from a PouchDB database as a JSON object.
* @param {string} props.docId - The ID of the document to be exported.
* @param {string} props.docName - The name of the document, used for naming the export file.
* @param {boolean} props.includeChild - A flag indicating whether to include child documents in the export.
* @returns {JSX.Element} A React element that renders the `ExportDoc` component with the exported data.
*/
const ExportDocWrapper: FC<ExportDocWrapperProps> = ({
docId,
docName,
includeChild,
}: ExportDocWrapperProps): JSX.Element => {
const db = useDB()
const [sendData, setSendData] = useState<JSONValue>({})

useEffect(() => {
exportDocumentAsJSONObject(db, docId, includeChild).then(data =>
setSendData(data),
)
}, [])

const timestamp = new Date(Date.now()).toUTCString()
return (
<ExportDoc sendData={sendData} fileName={docName + ' ' + timestamp} />
)
}

export default ExportDocWrapper
22 changes: 19 additions & 3 deletions src/components/home.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useState, type FC, useEffect, SetStateAction } from 'react'
import { ListGroup, Button, Modal } from 'react-bootstrap'
import { LinkContainer } from 'react-router-bootstrap'
import { TfiTrash, TfiPencil } from 'react-icons/tfi'
import { TfiTrash, TfiPencil, TfiArrowDown } from 'react-icons/tfi'
import { useNavigate } from 'react-router-dom'
import { deleteEmptyProjects, useDB } from '../utilities/database_utils'
import ImportDoc from './import_document_wrapper'
import ExportDoc from './export_document_wrapper'

/**
* Home: Renders the Home page for the APP
Expand Down Expand Up @@ -101,7 +103,7 @@ const Home: FC = () => {
}

const sortByEditTime = (jobsList: any[]) => {
const sortedJobsByEditTime = jobsList.sort((a, b) => {
jobsList.sort((a, b) => {
if (
a.metadata_.last_modified_at.toString() <
b.metadata_.last_modified_at.toString()
Expand All @@ -126,8 +128,9 @@ const Home: FC = () => {
const editAddressDetails = (projectID: string) => {
navigate('app/' + projectID, { replace: true })
}

const projects_display =
Object.keys(projectList).length == 0
Object.keys(projectList).length === 0
? []
: projectList.map((key, value) => (
<div key={key._id}>
Expand Down Expand Up @@ -158,6 +161,11 @@ const Home: FC = () => {
>
<TfiTrash size={22} />
</Button>
<ExportDoc
docId={key._id}
docName={key.metadata_?.doc_name}
includeChild={true}
/>
</span>
<b>{key.metadata_?.doc_name}</b>
{key.data_?.location?.street_address && (
Expand Down Expand Up @@ -213,6 +221,10 @@ const Home: FC = () => {
>
Add a New Project
</Button>
<ImportDoc
id="project_json"
label="Import a Project"
/>
</div>
</center>
)}
Expand All @@ -225,6 +237,10 @@ const Home: FC = () => {
>
Add a New Project
</Button>
<ImportDoc
id="project_json"
label="Import Project"
/>
</div>
{projects_display}
</div>
Expand Down
127 changes: 127 additions & 0 deletions src/components/import_document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { useEffect, useRef, useState } from 'react'
import type { ChangeEvent, FC, MouseEvent } from 'react'
import { Button } from 'react-bootstrap'
import { ImportDocumentIntoDB, useDB } from '../utilities/database_utils'
import { EXPORT_FILE_TYPE } from '../utilities/paths_utils'

interface ImportDocProps {
id: string
label: string
}

/**
* ImportDoc component : Imports JSON documents into a PouchDB database.
*
* This component handles the importation of a project document from a JSON file into a PouchDB database.
* It renders a button that, when clicked, triggers a hidden file input to open. When a file is selected,
* the file's content is read, processed, and imported into the database.
*
* @param label - The label for the import operation.
* @param file - The file to be imported.
*
* @returns The rendered button and hidden file input elements for importing a project.
*/
const ImportDoc: FC<ImportDocProps> = ({ id, label }) => {
// Create references to the hidden file inputs
const hiddenFileUploadInputRef = useRef<HTMLInputElement>(null)
const [projectNames, setProjectNames] = useState<string[]>([])
const [isFileProcessed, setIsFileProcessed] = useState<boolean>(false)
const [error, setError] = useState<String>('')
const db = useDB()

const handleFileInputButtonClick = (
event: MouseEvent<HTMLButtonElement>,
) => {
hiddenFileUploadInputRef.current?.click()
}

/**
* Fetches project names from the database and updates the state.
*
* @returns {Promise<void>} A promise that resolves when the fetch operation is complete.
*/
const fetchProjectNames = async (): Promise<void> => {
const result = await db.allDocs({
include_docs: true,
})
const projectDocs = result.rows
.map((row: any) => row.doc)
.filter((doc: any) => doc.type === 'project')

const projectNames = projectDocs.map(
(doc: any) => doc.metadata_.doc_name,
)
setProjectNames(projectNames)
}

useEffect(() => {
// Retrieve all project names from the database when each upload is processed
fetchProjectNames()
}, [projectNames, isFileProcessed])

const handleFileInputChange = async (
event: ChangeEvent<HTMLInputElement>,
) => {
if (event.target.files) {
const file = event.target.files[0]
if (file) {
const isValid = file.name.endsWith(EXPORT_FILE_TYPE)
if (!isValid) {
setError(
"Please select file with extension '" +
EXPORT_FILE_TYPE +
"'",
)
} else {
setError('')
processJsonData(file) // Processes JSON data from a file
}
}
event.target.value = ''
}
}

/**
* Processes JSON data from a file and imports it into the database.
*
* @param {File} file - The JSON file containing documents to be imported.
* @returns {Promise<void>} A promise that resolves when the processing is complete.
*/
const processJsonData = async (file: File): Promise<void> => {
// Reset state and local variables for every import
setIsFileProcessed(false)

const reader = new FileReader()
reader.readAsText(file)
reader.onload = async event => {
const dataFromFile = (event.target as FileReader).result
if (typeof dataFromFile !== 'string') {
console.error('File content is not a string.')
return
}
try {
const jsonData = JSON.parse(dataFromFile)
await ImportDocumentIntoDB(db, jsonData, projectNames)
} catch (error) {
console.error('Error parsing JSON from file:', error)
}
}
}
return (
<>
{' '}
&nbsp;
<Button onClick={handleFileInputButtonClick}>{label}</Button>
<input
accept={'*' + EXPORT_FILE_TYPE}
onChange={handleFileInputChange}
ref={hiddenFileUploadInputRef}
className="photo-upload-input"
type="file"
/>
{error && <div className="error">{error}</div>}
</>
)
}

export default ImportDoc
27 changes: 27 additions & 0 deletions src/components/import_document_wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { FC } from 'react'
import ImportDoc from './import_document'

// Define the props interface for ImportDocWrapper
interface ImportDocWrapperProps {
id: string
label: string
}

/**
* ImportDocWrapper component.
*
* This component wraps the `ImportDoc` component, retrieves attachments and job ID from the context, constructs
* a reference ID, and passes the appropriate file blob and label to `ImportDoc`.
*
* @param {string} label - The label to be displayed for the import operation.
*
* @returns {JSX.Element} The rendered ImportDoc component with context-provided props.
*/
const ImportDocWrapper: FC<ImportDocWrapperProps> = ({
id,
label,
}: ImportDocWrapperProps): JSX.Element => {
return <ImportDoc id={id} label={label} />
}

export default ImportDocWrapper
Loading
Loading