From 001c0d1033415bd15bad95ecedf05bfa3f0c2935 Mon Sep 17 00:00:00 2001 From: Sudha Eswaran Date: Wed, 9 Oct 2024 23:47:32 -0700 Subject: [PATCH 1/6] import and export of db documents --- src/components/export_document.tsx | 56 ++++++++ src/components/export_document_wrapper.tsx | 42 ++++++ src/components/home.tsx | 14 +- src/components/import_document.tsx | 115 +++++++++++++++ src/components/import_document_wrapper.tsx | 27 ++++ src/utilities/database_utils.tsx | 158 ++++++++++++++++++++- 6 files changed, 409 insertions(+), 3 deletions(-) create mode 100644 src/components/export_document.tsx create mode 100644 src/components/export_document_wrapper.tsx create mode 100644 src/components/import_document.tsx create mode 100644 src/components/import_document_wrapper.tsx diff --git a/src/components/export_document.tsx b/src/components/export_document.tsx new file mode 100644 index 0000000..80ea693 --- /dev/null +++ b/src/components/export_document.tsx @@ -0,0 +1,56 @@ +import React from 'react' +import JSONValue from '../types/json_value.type' +import { Button } from 'react-bootstrap' +import { TfiArrowDown } from 'react-icons/tfi' + +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 = ({ + sendData, + fileName, +}: any): JSX.Element => { + const handleDownload = () => { + // Create a Blob from the sendData + const blob = new Blob([sendData as BlobPart], { + type: 'application/octet-stream', + }) + 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}.qit`) + + // 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) + } + return ( + + ) +} + +export default ExportDoc diff --git a/src/components/export_document_wrapper.tsx b/src/components/export_document_wrapper.tsx new file mode 100644 index 0000000..8e9cda4 --- /dev/null +++ b/src/components/export_document_wrapper.tsx @@ -0,0 +1,42 @@ +import { FC, useEffect, useState } from 'react' +import ExportDoc from './export_document' +import { exportDocumentAsJSONObject } from '../utilities/database_utils' +import DBName from './db_details' +import PouchDB from 'pouchdb' +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 = ({ + docId, + docName, + includeChild, +}: ExportDocWrapperProps): JSX.Element => { + const db = new PouchDB(DBName) + const [sendData, setSendData] = useState({}) + + useEffect(() => { + exportDocumentAsJSONObject(db, docId, includeChild).then(data => + setSendData(data), + ) + }, []) + + const timestamp = new Date(Date.now()).toUTCString() + return ( + + ) +} + +export default ExportDocWrapper diff --git a/src/components/home.tsx b/src/components/home.tsx index 9bda9e9..f03e391 100644 --- a/src/components/home.tsx +++ b/src/components/home.tsx @@ -3,10 +3,12 @@ import { ListGroup, Button, Modal } from 'react-bootstrap' import { LinkContainer } from 'react-router-bootstrap' import { putNewProject } from '../utilities/database_utils' import PouchDB from 'pouchdb' -import { TfiTrash, TfiPencil } from 'react-icons/tfi' +import { TfiTrash, TfiPencil, TfiArrowDown } from 'react-icons/tfi' import dbName from './db_details' import { retrieveProjectDocs } from '../utilities/database_utils' import { useNavigate } from 'react-router-dom' +import ImportDoc from './import_document_wrapper' +import ExportDoc from './export_document_wrapper' /** * Home: Renders the Home page for the APP @@ -121,7 +123,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() @@ -146,6 +148,7 @@ const Home: FC = () => { const editAddressDetails = (projectID: string) => { navigate('app/' + projectID, { replace: true }) } + let projects_display: any = '' if (Object.keys(projectList).length == 0) { projects_display = ( @@ -171,6 +174,7 @@ const Home: FC = () => { + ) @@ -181,6 +185,7 @@ const Home: FC = () => { +

@@ -214,6 +219,11 @@ const Home: FC = () => { > + {key.metadata_?.doc_name} {key.data_?.location?.street_address && ( diff --git a/src/components/import_document.tsx b/src/components/import_document.tsx new file mode 100644 index 0000000..18ff7b0 --- /dev/null +++ b/src/components/import_document.tsx @@ -0,0 +1,115 @@ +import { useEffect, useRef, useState } from 'react' +import type { ChangeEvent, FC, MouseEvent } from 'react' +import { Button } from 'react-bootstrap' +import dbName from './db_details' +import PouchDB from 'pouchdb' +import { ImportDocumentIntoDB } from '../utilities/database_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 = ({ id, label }) => { + // Create references to the hidden file inputs + const hiddenFileUploadInputRef = useRef(null) + const [projectNames, setProjectNames] = useState([]) + const [isFileProcessed, setIsFileProcessed] = useState(false) + const db = new PouchDB(dbName) + + const handleFileInputButtonClick = ( + event: MouseEvent, + ) => { + hiddenFileUploadInputRef.current?.click() + } + + /** + * Fetches project names from the database and updates the state. + * + * @returns {Promise} A promise that resolves when the fetch operation is complete. + */ + const fetchProjectNames = async (): Promise => { + 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, + ) => { + if (event.target.files) { + const file = event.target.files[0] + if (file) processJsonData(file) // Processes JSON data from a file + // Reset input value to allow selecting the same file again + 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} A promise that resolves when the processing is complete. + */ + const processJsonData = async (file: File): Promise => { + // 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 ( + <> + {' '} +   + + + + ) +} + +export default ImportDoc diff --git a/src/components/import_document_wrapper.tsx b/src/components/import_document_wrapper.tsx new file mode 100644 index 0000000..5cba24d --- /dev/null +++ b/src/components/import_document_wrapper.tsx @@ -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 = ({ + id, + label, +}: ImportDocWrapperProps): JSX.Element => { + return +} + +export default ImportDocWrapper diff --git a/src/utilities/database_utils.tsx b/src/utilities/database_utils.tsx index d95a53d..81be3d5 100644 --- a/src/utilities/database_utils.tsx +++ b/src/utilities/database_utils.tsx @@ -250,13 +250,169 @@ function promisifiedDBInfo( } /** + * Exports a document from the database as a JSON object. + * @param {any} db - The database instance from which the document will be exported. + * @param {string} docId - The ID of the document to be exported. + * @param {boolean} includeChild - A flag indicating whether to include child documents in the export. + * @returns {Promise<{}>} A promise that resolves to the exported document as a JSON object. + **/ +export async function exportDocumentAsJSONObject( + db: any, + docId: string, + includeChild: boolean, +): Promise<{}> { + const docById = await db.get(docId, { + attachments: true, + revs_info: false, + }) + + if (includeChild) { + const childDocs: any = await db.allDocs({ + keys: docById.children, + include_docs: true, + attachments: true, + revs_info: false, + }) + + const combinedDocs = [ + docById, + ...childDocs.rows.map((row: { doc: any }) => row.doc), + ] + return JSON.stringify({ all_docs: combinedDocs }) + } + + return JSON.stringify({ all_docs: docById }) +} + +/** + * Finds the maximum index for a given document name in an array of names. * + * This function checks if any names in the provided array match the specified + * document name or the pattern of that name followed by an index in parentheses (e.g., "docName (1)"). + * It returns the highest index found among the matching names. If no matching names are found, + * it returns -1 to indicate the absence of matches. + * @param {string[]} names - An array of strings representing document names. + * @param {string} docName - The base document name to search for. + * @returns {number} The maximum index found for the document name, or -1 if no matches are found. + */ +const findMaxDocNameIndex = (names: string[], docName: string): number => { + // regex to match the document name with an optional index in parentheses. + const regex = new RegExp(`^${docName}( \\(\\d+\\))?$`) + return names.reduce((maxIndex, name) => { + if (regex.test(name)) { + const match = name.match(/\((\d+)\)/) + const index = match ? parseInt(match[1], 10) : 0 + return Math.max(maxIndex, index) + } + return maxIndex + }, -1) +} + +/** + * Updates the document name for a project to avoid duplicates. + * @param {any} input_doc - The document object that may contain a project. + * @returns {Promise} The updated document object with a unique name. + */ +const updateProjectName = async ( + input_doc: any, + docNames: string[], +): Promise => { + // Adjust doc_name for projects to avoid duplicates + if (input_doc.type === 'project') { + const doc_name = input_doc.metadata_.doc_name || '' + // finding the max index for doc_name, if the name is already present + const count = findMaxDocNameIndex(docNames, doc_name) + 1 + input_doc.metadata_.doc_name = + count > 0 ? `${doc_name} (${count})` : doc_name + } + return input_doc +} + +/** + * Updates the project.children with installationIds from imported installations. + * Retrieves the project document using projectId and updates its children field. + * @param {string} projectId - The ID of the project document to be updated. + * @param {string[]} installationIds - An array of installation IDs to be set as the project's children. + * @returns {Promise} A promise that resolves when the update operation is complete. */ +const updateProject = async ( + db: PouchDB.Database<{}>, + projectId: string, + installationIds: string[], +): Promise => { + if (projectId) { + const projectDoc: any = await db.get(projectId) + projectDoc.children = installationIds + try { + await db.put(projectDoc) + } catch (error) { + console.log( + 'Error in updating the installations in project doc in DB', + error, + ) + } + } +} + +/** + * Imports documents into the database. + * @param db - The database instance where the documents will be imported. + * @param jsonData - An object containing the documents along with the attachments to be imported, + * @param docNames - An array of document names to check and update unique project name for imported project doc. + * @returns A promise that resolves when the import is complete. + * + **/ +export async function ImportDocumentIntoDB( + db: PouchDB.Database<{}>, + jsonData: { all_docs: any }, + docNames: string[], +): Promise { + let projectId: string | null = null + let installationIds: string[] = [] + // Process each document in the JSON data + for (const input_doc of jsonData.all_docs) { + if (!input_doc || !input_doc.metadata_) continue + + // Clean the data for new doc creation + // remove the id and rev from the imported doc + delete input_doc._id + delete input_doc._rev + + // Check if the document is a project and update its name + // if the name is already present in the DB + const updated_doc = await updateProjectName(input_doc, docNames) + + const now = new Date() + updated_doc.metadata_.created_at = now + updated_doc.metadata_.last_modified_at = now + + const result = await db.post(input_doc) + // create a lis of the installationIds and set projectId based on document type + if (updated_doc.type === 'installation') { + installationIds.push(result.id) + } else { + projectId = result.id + } + } + // Update the imported project with the newly created installation IDs + if (projectId && installationIds) + updateProject(db, projectId, installationIds) +} + +/** + * Appends a child document to a project document in the database. + * + * @param db - The PouchDB database instance where the project document resides. + * @param docId - The ID of the project document to which the child document will be appended. + * @param childDocId - The ID of the child document to be appended. + * @returns {Promise<{}>} A promise that resolves to an object containing updated document. + * + * */ export async function appendChildToProject( db: PouchDB.Database<{}>, docId: string, childDocId: string, -): Promise { +): Promise<{}> { try { // Fetch the document, update the children array, and put it back in one go return await db.upsert(docId, doc => { From f2ca6ffdcdad409f789dd03465f98d6b67283c2c Mon Sep 17 00:00:00 2001 From: Sudha Eswaran Date: Tue, 15 Oct 2024 17:26:13 -0700 Subject: [PATCH 2/6] changes to the upload json objects --- package.json | 2 +- public/print.css | 8 ++++-- src/App.css | 8 ++++-- src/components/export_document.tsx | 40 ++++++++++++++++-------------- src/components/import_document.tsx | 20 ++++++++++++--- src/utilities/database_utils.tsx | 26 +++++++++---------- src/utilities/paths_utils.tsx | 4 +++ 7 files changed, 68 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 0852f83..17ed55d 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "heic2any": "^0.0.4", "html-webpack-plugin": "^5.5.0", "identity-obj-proxy": "^3.0.0", - "image-blob-reduce": "^4.1.0", "jest": "^27.4.3", "jest-resolve": "^27.4.2", "jest-watch-typeahead": "^1.0.0", @@ -137,6 +136,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.22.9", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@types/react-router-bootstrap": "^0.26.0", "@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/parser": "^5.0.0", diff --git a/public/print.css b/public/print.css index 20dc411..778ab6b 100644 --- a/public/print.css +++ b/public/print.css @@ -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; @@ -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; } \ No newline at end of file diff --git a/src/App.css b/src/App.css index 20dc411..778ab6b 100644 --- a/src/App.css +++ b/src/App.css @@ -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; @@ -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; } \ No newline at end of file diff --git a/src/components/export_document.tsx b/src/components/export_document.tsx index 80ea693..a784855 100644 --- a/src/components/export_document.tsx +++ b/src/components/export_document.tsx @@ -1,7 +1,8 @@ import React from 'react' import JSONValue from '../types/json_value.type' import { Button } from 'react-bootstrap' -import { TfiArrowDown } from 'react-icons/tfi' +import { TfiImport } from 'react-icons/tfi' +import { EXPORT_FILE_TYPE } from '../utilities/paths_utils' interface ExportDocProps { sendData: JSONValue // The data to be downloaded @@ -13,31 +14,34 @@ interface ExportDocProps { * @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 = ({ sendData, fileName, }: any): JSX.Element => { const handleDownload = () => { - // Create a Blob from the sendData - const blob = new Blob([sendData as BlobPart], { - type: 'application/octet-stream', - }) - const url = URL.createObjectURL(blob) + 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}.qit`) + // 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) + // 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) + // Clean up the URL object + URL.revokeObjectURL(url) + } catch (error) { + console.log('Error in exporting the document', error) + } } return ( ) } diff --git a/src/components/import_document.tsx b/src/components/import_document.tsx index 18ff7b0..e84b30f 100644 --- a/src/components/import_document.tsx +++ b/src/components/import_document.tsx @@ -4,6 +4,7 @@ import { Button } from 'react-bootstrap' import dbName from './db_details' import PouchDB from 'pouchdb' import { ImportDocumentIntoDB } from '../utilities/database_utils' +import { EXPORT_FILE_TYPE } from '../utilities/paths_utils' interface ImportDocProps { id: string @@ -27,6 +28,7 @@ const ImportDoc: FC = ({ id, label }) => { const hiddenFileUploadInputRef = useRef(null) const [projectNames, setProjectNames] = useState([]) const [isFileProcessed, setIsFileProcessed] = useState(false) + const [error, setError] = useState('') const db = new PouchDB(dbName) const handleFileInputButtonClick = ( @@ -64,8 +66,19 @@ const ImportDoc: FC = ({ id, label }) => { ) => { if (event.target.files) { const file = event.target.files[0] - if (file) processJsonData(file) // Processes JSON data from a file - // Reset input value to allow selecting the same file again + 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 = '' } } @@ -102,12 +115,13 @@ const ImportDoc: FC = ({ id, label }) => {   + {error &&
{error}
} ) } diff --git a/src/utilities/database_utils.tsx b/src/utilities/database_utils.tsx index 81be3d5..cd230d7 100644 --- a/src/utilities/database_utils.tsx +++ b/src/utilities/database_utils.tsx @@ -260,7 +260,7 @@ export async function exportDocumentAsJSONObject( db: any, docId: string, includeChild: boolean, -): Promise<{}> { +): Promise { const docById = await db.get(docId, { attachments: true, revs_info: false, @@ -289,23 +289,21 @@ export async function exportDocumentAsJSONObject( * * This function checks if any names in the provided array match the specified * document name or the pattern of that name followed by an index in parentheses (e.g., "docName (1)"). - * It returns the highest index found among the matching names. If no matching names are found, - * it returns -1 to indicate the absence of matches. * @param {string[]} names - An array of strings representing document names. * @param {string} docName - The base document name to search for. - * @returns {number} The maximum index found for the document name, or -1 if no matches are found. + * @returns {string} The document name appended by the maximum index of a given document name. */ -const findMaxDocNameIndex = (names: string[], docName: string): number => { +const findMaxDocNameIndex = (names: string[], docName: string): string => { // regex to match the document name with an optional index in parentheses. - const regex = new RegExp(`^${docName}( \\(\\d+\\))?$`) - return names.reduce((maxIndex, name) => { - if (regex.test(name)) { - const match = name.match(/\((\d+)\)/) + let count = names.reduce((maxIndex, name) => { + if (names.includes(docName)) { + const match = name.match(/\((\d+)\)[^()]*$/) const index = match ? parseInt(match[1], 10) : 0 return Math.max(maxIndex, index) } - return maxIndex + return -1 }, -1) + return count >= 0 ? `${docName} (${count + 1})` : docName } /** @@ -320,10 +318,8 @@ const updateProjectName = async ( // Adjust doc_name for projects to avoid duplicates if (input_doc.type === 'project') { const doc_name = input_doc.metadata_.doc_name || '' - // finding the max index for doc_name, if the name is already present - const count = findMaxDocNameIndex(docNames, doc_name) + 1 - input_doc.metadata_.doc_name = - count > 0 ? `${doc_name} (${count})` : doc_name + // add the max index for doc_name in the end of the doc name, if the name is already present + input_doc.metadata_.doc_name = findMaxDocNameIndex(docNames, doc_name) } return input_doc } @@ -382,6 +378,8 @@ export async function ImportDocumentIntoDB( // if the name is already present in the DB const updated_doc = await updateProjectName(input_doc, docNames) + console.log(updated_doc) + const now = new Date() updated_doc.metadata_.created_at = now updated_doc.metadata_.last_modified_at = now diff --git a/src/utilities/paths_utils.tsx b/src/utilities/paths_utils.tsx index 72afc36..09a68b6 100644 --- a/src/utilities/paths_utils.tsx +++ b/src/utilities/paths_utils.tsx @@ -26,3 +26,7 @@ export function pathToId( } return [prefix, ...toPath(path)].join(separator) } + +// File extension for the project doc(JSON data) downloaded from the QI tool. +// Expected extension: .qit.json +export const EXPORT_FILE_TYPE = '.qit.json' From 84e01cb6b4ac28a96fe3f18508bca96bc6d079c3 Mon Sep 17 00:00:00 2001 From: Sudha Eswaran Date: Tue, 15 Oct 2024 17:45:04 -0700 Subject: [PATCH 3/6] format changes --- src/components/home.tsx | 216 ++++++++++++++++--------------- src/utilities/database_utils.tsx | 2 - 2 files changed, 114 insertions(+), 104 deletions(-) diff --git a/src/components/home.tsx b/src/components/home.tsx index f03e391..30c85bc 100644 --- a/src/components/home.tsx +++ b/src/components/home.tsx @@ -149,111 +149,123 @@ const Home: FC = () => { navigate('app/' + projectID, { replace: true }) } - let projects_display: any = '' - if (Object.keys(projectList).length == 0) { - projects_display = ( -
-
-

- Welcome to the Quality Install Tool -

-
-

- With this tool you will be able
to easily take photos - and document
- your entire installation project.
-
-
- For your records -
- For your clients -
- For quality assurance reporting -

-
- - -
-
- ) - } else { - projects_display = [ -
-
- - -
-
-
-
, - projectList.map((key, value) => ( -
- - - - - {/* */} + const projects_display = + Object.keys(projectList).length === 0 + ? [] + : projectList.map((key, value) => ( +
+ + + + + {/* */} + + + + + + {key.metadata_?.doc_name} + {key.data_?.location?.street_address && ( + <> +
+ {key.data_?.location?.street_address}, + + )} + {key.data_?.location?.city && ( + <> +
+ {key.data_?.location?.city},{' '} + + )} + {key.data_.location?.state && ( + <>{key.data_?.location?.state} + )} + {key.data_.location?.zip_code && ( + <>{key.data_?.location?.zip_code} + )} + + + +
+ )) - - - - - {key.metadata_?.doc_name} - {key.data_?.location?.street_address && ( - <> -
- {key.data_?.location?.street_address}, - - )} - {key.data_?.location?.city && ( - <> -
- {key.data_?.location?.city},{' '} - - )} - {key.data_.location?.state && ( - <>{key.data_?.location?.state} - )} - {key.data_.location?.zip_code && ( - <>{key.data_?.location?.zip_code} - )} - - - -
- )), - ] - } return ( <> - {projects_display} +
+ {Object.keys(projectList).length == 0 && ( +
+
+

+ Welcome to the Quality Install Tool +

+
+

+ With this tool you will be able
to easily + take photos and document
+ your entire installation project.
+
+
+ For your records +
+ For your clients +
+ For quality assurance reporting +

+
+ + +
+
+ )} + {Object.keys(projectList).length > 0 && ( +
+
+ + +
+ {projects_display} +
+ )} +

diff --git a/src/utilities/database_utils.tsx b/src/utilities/database_utils.tsx index cd230d7..4c8f8cc 100644 --- a/src/utilities/database_utils.tsx +++ b/src/utilities/database_utils.tsx @@ -378,8 +378,6 @@ export async function ImportDocumentIntoDB( // if the name is already present in the DB const updated_doc = await updateProjectName(input_doc, docNames) - console.log(updated_doc) - const now = new Date() updated_doc.metadata_.created_at = now updated_doc.metadata_.last_modified_at = now From 452fa4dc93ba02ad6b3686cbef5fc9efcb29b01d Mon Sep 17 00:00:00 2001 From: Sudha Eswaran Date: Mon, 21 Oct 2024 12:13:42 -0700 Subject: [PATCH 4/6] merged changes from main --- src/components/export_document_wrapper.tsx | 6 +- src/components/home.tsx | 114 ++++++++++----------- src/components/import_document.tsx | 8 +- 3 files changed, 62 insertions(+), 66 deletions(-) diff --git a/src/components/export_document_wrapper.tsx b/src/components/export_document_wrapper.tsx index 8e9cda4..d764594 100644 --- a/src/components/export_document_wrapper.tsx +++ b/src/components/export_document_wrapper.tsx @@ -1,8 +1,6 @@ import { FC, useEffect, useState } from 'react' import ExportDoc from './export_document' -import { exportDocumentAsJSONObject } from '../utilities/database_utils' -import DBName from './db_details' -import PouchDB from 'pouchdb' +import { exportDocumentAsJSONObject, useDB } from '../utilities/database_utils' import JSONValue from '../types/json_value.type' // Define the props interface for ExportDocWrapper @@ -24,7 +22,7 @@ const ExportDocWrapper: FC = ({ docName, includeChild, }: ExportDocWrapperProps): JSX.Element => { - const db = new PouchDB(DBName) + const db = useDB() const [sendData, setSendData] = useState({}) useEffect(() => { diff --git a/src/components/home.tsx b/src/components/home.tsx index b857ba2..a24c089 100644 --- a/src/components/home.tsx +++ b/src/components/home.tsx @@ -133,64 +133,64 @@ const Home: FC = () => { Object.keys(projectList).length === 0 ? [] : projectList.map((key, value) => ( -

- - - - - {/* */} +
+ + + + + {/* */} - - - - - {key.metadata_?.doc_name} - {key.data_?.location?.street_address && ( - <> -
- {key.data_?.location?.street_address}, - - )} - {key.data_?.location?.city && ( - <> -
- {key.data_?.location?.city},{' '} - - )} - {key.data_.location?.state && ( - <>{key.data_?.location?.state} - )} - {key.data_.location?.zip_code && ( - <>{key.data_?.location?.zip_code} - )} - - - -
- )) + + + + + {key.metadata_?.doc_name} + {key.data_?.location?.street_address && ( + <> +
+ {key.data_?.location?.street_address}, + + )} + {key.data_?.location?.city && ( + <> +
+ {key.data_?.location?.city},{' '} + + )} + {key.data_.location?.state && ( + <>{key.data_?.location?.state} + )} + {key.data_.location?.zip_code && ( + <>{key.data_?.location?.zip_code} + )} + + + +
+ )) return ( <> diff --git a/src/components/import_document.tsx b/src/components/import_document.tsx index e84b30f..5215f88 100644 --- a/src/components/import_document.tsx +++ b/src/components/import_document.tsx @@ -1,9 +1,7 @@ import { useEffect, useRef, useState } from 'react' import type { ChangeEvent, FC, MouseEvent } from 'react' import { Button } from 'react-bootstrap' -import dbName from './db_details' -import PouchDB from 'pouchdb' -import { ImportDocumentIntoDB } from '../utilities/database_utils' +import { ImportDocumentIntoDB, useDB } from '../utilities/database_utils' import { EXPORT_FILE_TYPE } from '../utilities/paths_utils' interface ImportDocProps { @@ -29,7 +27,7 @@ const ImportDoc: FC = ({ id, label }) => { const [projectNames, setProjectNames] = useState([]) const [isFileProcessed, setIsFileProcessed] = useState(false) const [error, setError] = useState('') - const db = new PouchDB(dbName) + const db = useDB() const handleFileInputButtonClick = ( event: MouseEvent, @@ -115,7 +113,7 @@ const ImportDoc: FC = ({ id, label }) => {   Date: Wed, 6 Nov 2024 13:58:18 -0800 Subject: [PATCH 5/6] updating the importing doc name correctly --- src/utilities/database_utils.tsx | 35 +++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/utilities/database_utils.tsx b/src/utilities/database_utils.tsx index 2207fb5..220e493 100644 --- a/src/utilities/database_utils.tsx +++ b/src/utilities/database_utils.tsx @@ -342,16 +342,31 @@ export async function exportDocumentAsJSONObject( * @returns {string} The document name appended by the maximum index of a given document name. */ const findMaxDocNameIndex = (names: string[], docName: string): string => { - // regex to match the document name with an optional index in parentheses. - let count = names.reduce((maxIndex, name) => { - if (names.includes(docName)) { - const match = name.match(/\((\d+)\)[^()]*$/) - const index = match ? parseInt(match[1], 10) : 0 - return Math.max(maxIndex, index) - } - return -1 - }, -1) - return count >= 0 ? `${docName} (${count + 1})` : docName + const baseDocName = docName.replace(/\(\d+\)$/, '') // Remove any existing index from the docName + + // Check if the docName (with or without index) already exists in the list + const nameExists = names.some(name => name.startsWith(baseDocName)) + + // If the name exists, find the next available index, otherwise return the name as is + let count = 0 + if (nameExists) { + count = names.reduce((maxIndex, name) => { + // considering names that start with the same base name + if (name.startsWith(baseDocName)) { + // Match the index in parentheses (e.g., "Project(2)", "Project(3)") + const match = name.match(/\((\d+)\)$/) + + // Extracting the index or default to 0 if no index is found + const index = match && match[1] ? parseInt(match[1], 10) : 0 + + // Updating the maximum index if the current index is higher + return Math.max(maxIndex, index) + } + return maxIndex + }, 0) // Starts with 0 as the default if no matches are found + } + + return nameExists ? `${baseDocName} (${count + 1})` : docName } /** From 5636c7d0104d666dfc0068e55c265660118efc04 Mon Sep 17 00:00:00 2001 From: Sudha Eswaran Date: Wed, 6 Nov 2024 15:24:57 -0800 Subject: [PATCH 6/6] doc name updated --- src/utilities/database_utils.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/utilities/database_utils.tsx b/src/utilities/database_utils.tsx index 220e493..800ddda 100644 --- a/src/utilities/database_utils.tsx +++ b/src/utilities/database_utils.tsx @@ -342,10 +342,16 @@ export async function exportDocumentAsJSONObject( * @returns {string} The document name appended by the maximum index of a given document name. */ const findMaxDocNameIndex = (names: string[], docName: string): string => { - const baseDocName = docName.replace(/\(\d+\)$/, '') // Remove any existing index from the docName + const baseDocName = docName + .replace(/\(\d+\)$/, '') + .trim() + .replace(/\(\d+\)$/, '') + .trim() // Removing any existing index from the docName // Check if the docName (with or without index) already exists in the list - const nameExists = names.some(name => name.startsWith(baseDocName)) + const nameExists = names.some(name => { + return name === baseDocName || name.startsWith(baseDocName) + }) // If the name exists, find the next available index, otherwise return the name as is let count = 0 @@ -366,7 +372,7 @@ const findMaxDocNameIndex = (names: string[], docName: string): string => { }, 0) // Starts with 0 as the default if no matches are found } - return nameExists ? `${baseDocName} (${count + 1})` : docName + return nameExists ? `${baseDocName} (${count + 1})` : baseDocName } /**