From d26fad9b25fb3a53f220f8734f42e15d20afd647 Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Tue, 17 Dec 2024 10:37:01 -0700 Subject: [PATCH 01/16] 74 : Added text input to the PhotoInput. Adjusted some parameter descriptions. --- src/components/photo_input.tsx | 11 ++++++++ src/components/text_input.tsx | 4 +-- yarn.lock | 51 +++++++++++++++++++++++++++------- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/components/photo_input.tsx b/src/components/photo_input.tsx index 8e7d1095..0c4e71b7 100644 --- a/src/components/photo_input.tsx +++ b/src/components/photo_input.tsx @@ -9,6 +9,7 @@ import GpsCoordStr from './gps_coord_str' import type PhotoMetaData from '../types/photo_metadata.type' import { PHOTO_MIME_TYPES } from '../utilities/photo_utils' import { TfiTrash } from 'react-icons/tfi' +import TextInput from './text_input' interface PhotoInputProps { children: React.ReactNode @@ -61,6 +62,7 @@ const PhotoInput: FC = ({ const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false) const [cameraAvailable, setCameraAvailable] = useState(false) + const [noteValue, setNoteValue] = useState('') // Handle button clicks const handlePhotoCaptureButtonClick = ( @@ -138,6 +140,15 @@ const PhotoInput: FC = ({ and may be a

. Nested

s are not allowed, so we use a

*/} {children} + {uploadable ? ( = ({ diff --git a/yarn.lock b/yarn.lock index 4ae36af3..d68d2c8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2626,12 +2626,10 @@ dependencies: "@types/react" "*" -"@types/react-dom@^18.3.1": - version "18.3.1" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.1.tgz#1e4654c08a9cdcfb6594c780ac59b55aad42fe07" - integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ== - dependencies: - "@types/react" "*" +"@types/react-dom@^18.2.0": + version "18.3.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.5.tgz#45f9f87398c5dcea085b715c58ddcf1faf65f716" + integrity sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q== "@types/react-router-bootstrap@^0.26.0": version "0.26.6" @@ -2647,7 +2645,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@>=16", "@types/react@>=16.9.11", "@types/react@^18.0.0": +"@types/react@*", "@types/react@>=16", "@types/react@>=16.9.11": version "18.3.10" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.10.tgz#6edc26dc22ff8c9c226d3c7bf8357b013c842219" integrity sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg== @@ -2655,6 +2653,14 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/react@^18.2.0": + version "18.3.17" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.17.tgz#d86ca0e081c7a5e979b7db175f9515a41038cea7" + integrity sha512-opAQ5no6LqJNo9TqnxBKsgnkIYHozW9KSTlFVoSUJYh1Fl/sswkEoqIugRSm7tbh6pABtYjGAjW+GOS23j8qbw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -10927,7 +10933,16 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -11043,7 +11058,14 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -12304,7 +12326,16 @@ workbox-window@6.6.1: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 65b928dc97aaa71b4df9d0ae5c9673611b0543ee Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Tue, 17 Dec 2024 14:00:13 -0700 Subject: [PATCH 02/16] 74: Got one comment to come through. --- src/components/photo_input.tsx | 9 +- src/components/photo_input_wrapper.tsx | 12 + src/components/text_input_wrapper.tsx | 1 + .../doe_workflow_heat_pump_ducted.mdx | 367 +++++++++--------- 4 files changed, 204 insertions(+), 185 deletions(-) diff --git a/src/components/photo_input.tsx b/src/components/photo_input.tsx index 0c4e71b7..74d26081 100644 --- a/src/components/photo_input.tsx +++ b/src/components/photo_input.tsx @@ -22,6 +22,9 @@ interface PhotoInputProps { loading: boolean error: string count: number + data?: any + noteValue: string + updateNoteValue: (value: string) => void } // TODO: Determine whether or not the useEffect() method is needed. // We don't seem to need a separate camera button on an Android phone. @@ -40,6 +43,7 @@ interface PhotoInputProps { * @param uploadable When set, the PhotoInput component will open the gallery to upload the photo. * When unset, the PhotoInput component will use device camera for taking new photo (default). * @param loader When set, a loading image will be displayed during the upload process. + * @param updateNoteValue Function for setting the note */ const PhotoInput: FC = ({ children, @@ -52,6 +56,8 @@ const PhotoInput: FC = ({ loading, error, count, + noteValue, + updateNoteValue, }) => { // Create references to the hidden file inputs const hiddenPhotoCaptureInputRef = useRef(null) @@ -62,7 +68,6 @@ const PhotoInput: FC = ({ const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false) const [cameraAvailable, setCameraAvailable] = useState(false) - const [noteValue, setNoteValue] = useState('') // Handle button clicks const handlePhotoCaptureButtonClick = ( @@ -144,7 +149,7 @@ const PhotoInput: FC = ({ id="TEST-ID" label="Optional note about photo(s):" value={noteValue} - updateValue={setNoteValue} + updateValue={updateNoteValue} min={0} max={300} regexp={/.*/} //any string diff --git a/src/components/photo_input_wrapper.tsx b/src/components/photo_input_wrapper.tsx index 23b37f48..3259102d 100644 --- a/src/components/photo_input_wrapper.tsx +++ b/src/components/photo_input_wrapper.tsx @@ -14,6 +14,7 @@ interface PhotoInputWrapperProps { label: string uploadable: boolean count?: number + path?: string } /** @@ -26,6 +27,7 @@ interface PhotoInputWrapperProps { * @param label The label of the PhotoInput component * @param uploadable When set, the PhotoInput component will open the gallery to upload the photo. * When unset, the PhotoInput component will use device camera for taking new photo. + * * @param path The path (consistent with the path provided to the lodash */ const PhotoInputWrapper: FC = ({ children, @@ -33,6 +35,7 @@ const PhotoInputWrapper: FC = ({ label, uploadable, count = 10, + path, }) => { const [loading, setLoading] = useState(false) // Loading state const [error, setError] = useState('') // Loading state @@ -109,7 +112,11 @@ const PhotoInputWrapper: FC = ({ attachments, upsertAttachment, deleteAttachment, + data, + upsertData, }) => { + const photoNoteId = data[id] + const deletePhoto = (photoId: string) => { deleteAttachment(photoId) } @@ -209,6 +216,11 @@ const PhotoInputWrapper: FC = ({ loading={loading} error={error} count={count} + updateNoteValue={(value: any) => + upsertData(`${id}_note`, value) + } + noteValue={} + // {data[id] ?? data[id] : ""} > {children} diff --git a/src/components/text_input_wrapper.tsx b/src/components/text_input_wrapper.tsx index f5dd2d87..9d266c2f 100644 --- a/src/components/text_input_wrapper.tsx +++ b/src/components/text_input_wrapper.tsx @@ -30,6 +30,7 @@ const TextInputWrapper: FC = ({ max = 10240, regexp = /.*/, }) => { + debugger // Generate an id for the input const id = pathToId(path, 'input') diff --git a/src/templates/doe_workflow_heat_pump_ducted.mdx b/src/templates/doe_workflow_heat_pump_ducted.mdx index 4197b062..1eb03f77 100644 --- a/src/templates/doe_workflow_heat_pump_ducted.mdx +++ b/src/templates/doe_workflow_heat_pump_ducted.mdx @@ -3,91 +3,91 @@ ## Visual Assessment of the Ductwork - If the ducts will be entirely replaced or the visual inspection found significant duct upgrades and - repairs are needed: - - Make notes and skip performing a duct leakage test at the initial phase - - Perform the duct leakage test post upgrades and repairs to verify the ducts are sufficiently airtight - + If the ducts will be entirely replaced or the visual inspection found significant duct upgrades and + repairs are needed: + - Make notes and skip performing a duct leakage test at the initial phase + - Perform the duct leakage test post upgrades and repairs to verify the ducts are sufficiently airtight + Provide a representative photo of the existing ductwork condition. - + - + ## Static Pressure Test Total external static pressure measurement Take a photo of the manometer readout or screenshot from digital instrument app. - + ## Airflow Test Use flow plate test OR the pressure matching technique Take a photo of the airflow test setup. - + Take a photo of the manometer CFM or upload a screenshot of the instrument app. - + ## Duct Leakage Testing - If the ducts will be entirely replaced or the visual inspection found significant duct upgrades and + If the ducts will be entirely replaced or the visual inspection found significant duct upgrades and repairs are needed: - Make notes and skip performing a duct leakage test at the initial phase - Perform the duct leakage test post upgrades and repairs to verify the ducts are sufficiently airtight = ({ )} {error &&
{error}
} {photos?.length < count && ( -
+
)} + Date: Wed, 18 Dec 2024 11:27:24 -0700 Subject: [PATCH 05/16] 74 : Refactored and cleaned up code. --- src/components/photo_input.tsx | 10 +++--- src/components/photo_input_wrapper.tsx | 45 +++++++++++--------------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/components/photo_input.tsx b/src/components/photo_input.tsx index 2a873534..9e38284c 100644 --- a/src/components/photo_input.tsx +++ b/src/components/photo_input.tsx @@ -23,9 +23,9 @@ interface PhotoInputProps { loading: boolean error: string count: number - data?: any noteValue: JSONValue | undefined updateNoteValue: (value: any) => void + id: string } // TODO: Determine whether or not the useEffect() method is needed. // We don't seem to need a separate camera button on an Android phone. @@ -44,12 +44,13 @@ interface PhotoInputProps { * @param uploadable When set, the PhotoInput component will open the gallery to upload the photo. * When unset, the PhotoInput component will use device camera for taking new photo (default). * @param loader When set, a loading image will be displayed during the upload process. - * @param updateNoteValue Function for setting the note + * @param noteValue The value to populate the photo caption box + * @param updateNoteValue Function for setting the note in the photo caption box + * @param id Attachment id */ const PhotoInput: FC = ({ children, label, - metadata, photos, upsertPhoto, deletePhoto, @@ -59,6 +60,7 @@ const PhotoInput: FC = ({ count, noteValue, updateNoteValue, + id, }) => { debugger // Create references to the hidden file inputs @@ -250,7 +252,7 @@ const PhotoInput: FC = ({
)} = ({ data, upsertData, }) => { - debugger const deletePhoto = (photoId: string) => { deleteAttachment(photoId) } @@ -202,29 +218,6 @@ const PhotoInputWrapper: FC = ({ } } - interface JSONObject { - [key: string]: JSONValue - } - - function convertDataToNewData(data: {}): JSONObject { - let jsonObject = data as JSONObject - let newData: JSONObject = {} - - // Add logic here to convert data into a newData object whose values can be accessed using newData[id] - for (const key in jsonObject) { - if (jsonObject.hasOwnProperty(key)) { - newData[key] = jsonObject[key] - } - } - - return newData - } - - const newData = convertDataToNewData(data) - const noteValue = newData[`${id}_note`] - console.log(noteValue) - debugger - return ( <> = ({ updateNoteValue={(value: any) => upsertData(`${id}_note`, value) } - noteValue={noteValue} - // {data[id] ?? data[id] : ""} + noteValue={convertDataObject(data)[`${id}_note`]} + id={id} > {children} From 293b07b70cea8b2aaf94802a5542743af47f197b Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Wed, 18 Dec 2024 11:33:20 -0700 Subject: [PATCH 06/16] 74 : Cleaned up code, removed debuggers. --- src/components/photo_input.tsx | 3 +-- src/components/photo_input_wrapper.tsx | 3 --- src/components/store.tsx | 1 - src/components/text_input_wrapper.tsx | 1 - src/templates/doe_workflow_heat_pump_ducted.mdx | 2 +- 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/photo_input.tsx b/src/components/photo_input.tsx index 9e38284c..33e1d050 100644 --- a/src/components/photo_input.tsx +++ b/src/components/photo_input.tsx @@ -24,7 +24,7 @@ interface PhotoInputProps { error: string count: number noteValue: JSONValue | undefined - updateNoteValue: (value: any) => void + updateNoteValue: (value: string) => void id: string } // TODO: Determine whether or not the useEffect() method is needed. @@ -62,7 +62,6 @@ const PhotoInput: FC = ({ updateNoteValue, id, }) => { - debugger // Create references to the hidden file inputs const hiddenPhotoCaptureInputRef = useRef(null) const hiddenPhotoUploadInputRef = useRef(null) diff --git a/src/components/photo_input_wrapper.tsx b/src/components/photo_input_wrapper.tsx index 0400c7b3..a52e8b63 100644 --- a/src/components/photo_input_wrapper.tsx +++ b/src/components/photo_input_wrapper.tsx @@ -15,7 +15,6 @@ interface PhotoInputWrapperProps { label: string uploadable: boolean count?: number - path?: string } interface JSONObject { @@ -45,7 +44,6 @@ function convertDataObject(data: {}): JSONObject { * @param label The label of the PhotoInput component * @param uploadable When set, the PhotoInput component will open the gallery to upload the photo. * When unset, the PhotoInput component will use device camera for taking new photo. - * * @param path The path (consistent with the path provided to the lodash */ const PhotoInputWrapper: FC = ({ children, @@ -53,7 +51,6 @@ const PhotoInputWrapper: FC = ({ label, uploadable, count = 10, - path, }) => { const [loading, setLoading] = useState(false) // Loading state const [error, setError] = useState('') // Loading state diff --git a/src/components/store.tsx b/src/components/store.tsx index a5b95413..d8beb25a 100644 --- a/src/components/store.tsx +++ b/src/components/store.tsx @@ -291,7 +291,6 @@ export const StoreProvider: FC = ({ * @param value The value that is to be updated/inserted */ const upsertData: UpsertData = (pathStr, value) => { - debugger pathStr = 'data_.' + pathStr upsertDoc(pathStr, value) } diff --git a/src/components/text_input_wrapper.tsx b/src/components/text_input_wrapper.tsx index 9d266c2f..f5dd2d87 100644 --- a/src/components/text_input_wrapper.tsx +++ b/src/components/text_input_wrapper.tsx @@ -30,7 +30,6 @@ const TextInputWrapper: FC = ({ max = 10240, regexp = /.*/, }) => { - debugger // Generate an id for the input const id = pathToId(path, 'input') diff --git a/src/templates/doe_workflow_heat_pump_ducted.mdx b/src/templates/doe_workflow_heat_pump_ducted.mdx index 1eb03f77..fe891bf1 100644 --- a/src/templates/doe_workflow_heat_pump_ducted.mdx +++ b/src/templates/doe_workflow_heat_pump_ducted.mdx @@ -7,7 +7,7 @@ repairs are needed: - Make notes and skip performing a duct leakage test at the initial phase - Perform the duct leakage test post upgrades and repairs to verify the ducts are sufficiently airtight - + Provide a representative photo of the existing ductwork condition. From 302fa3a01d7a2da74625d11df078aead4553873a Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Wed, 18 Dec 2024 15:33:44 -0700 Subject: [PATCH 07/16] 74: Added note field to the component. --- src/App.css | 504 +++++++++++++++---------------- src/components/photo.tsx | 15 +- src/components/photo_wrapper.tsx | 45 ++- 3 files changed, 299 insertions(+), 265 deletions(-) diff --git a/src/App.css b/src/App.css index 37f08c59..159dc31c 100644 --- a/src/App.css +++ b/src/App.css @@ -1,443 +1,444 @@ .App { - text-align: center; + text-align: center; } .App-logo { - height: 40vmin; - pointer-events: none; + height: 40vmin; + pointer-events: none; } @media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } + .App-logo { + animation: App-logo-spin infinite 20s linear; + } } -.align-right{ - text-align: right; +.align-right { + text-align: right; } .App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; } .App-link { - color: #61dafb; + color: #61dafb; } @keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } } /* Code below are added by PNNL */ /*Headings*/ h1 { - font-size: 1.5rem; - text-align: center; - break-after: avoid-page; - justify-self: center; + font-size: 1.5rem; + text-align: center; + break-after: avoid-page; + justify-self: center; } h2 { - break-after: avoid-page; - font-size: 1.25rem; + break-after: avoid-page; + font-size: 1.25rem; } -h2:not(.accordion-header, h1+h2) { - margin-top: 30px; +h2:not(.accordion-header, h1 + h2) { + margin-top: 30px; } h3 { - font-size: 1rem; - break-after: avoid-page; + font-size: 1rem; + break-after: avoid-page; } /* used in all templates - for report title */ h1 + h2 { - text-align: center; + text-align: center; } /* used in mdx_project_details_view.tsx */ h1 + h3 { - text-align: center; + text-align: center; } /* used in workflows_view.tsx, mdx_template_view.tsx, job_view.tsx */ -.address -{ - text-align: center; +.address { + text-align: center; } /* used in home.tsx */ .flex-container { - display: flex; - flex-direction: row; - gap: 10px; - align-items: flex-start; - align-content: center; - justify-content: space-around; + display: flex; + flex-direction: row; + gap: 10px; + align-items: flex-start; + align-content: center; + justify-content: space-around; } .flex-child { - flex: 1; - align-self: top; + flex: 1; + align-self: top; } /* used in jobs_view.tsx */ .icon-container { - float: right; - vertical-align: top; + float: right; + vertical-align: top; } .button-container-right { - padding-left: 0.75rem; - padding-right: 0.75rem; - float: right; + padding-left: 0.75rem; + padding-right: 0.75rem; + float: right; } .button-container-center { - float: center; - padding-left: 0.75rem; - padding-right: 0.75rem; + float: center; + padding-left: 0.75rem; + padding-right: 0.75rem; } - - .menu-container { - float: right; - vertical-align: top; + float: right; + vertical-align: top; } - .img-thumbnail { - object-fit: contain; - max-height: 500px; + object-fit: contain; + max-height: 500px; } /* jobs_view.tsx & collapsible.tsx */ .bottom-margin { - margin-bottom: 10px; + margin-bottom: 10px; } +.string-input-modal { + .error { + color: red; + font-style: italic; + } +} -.string-input-modal{ - .error{ +.error { color: red; font-style: italic; - } -} - -.error{ - color: red; - font-style: italic; } - /* root_layout.tsx */ #root-flex-layout { - display: flex; - align-items: center; - justify-content: center; + display: flex; + align-items: center; + justify-content: center; } #root-background { - margin-left: auto; - margin-right: auto; - max-width: 800px; - background-color: rgba(231, 231, 231); - min-height: 100vh; + margin-left: auto; + margin-right: auto; + max-width: 800px; + background-color: rgba(231, 231, 231); + min-height: 100vh; } #root-banner { - background-color: #006100; + background-color: #006100; } #root-title { - color: gold; - font-size: 2rem; + color: gold; + font-size: 2rem; } #root-body { - padding-top: 1rem; - padding-bottom: 1rem; + padding-top: 1rem; + padding-bottom: 1rem; } #back-button-logo { - color: white; - height: 100%; + color: white; + height: 100%; } #back-button { - padding: 1rem; - background-color: #006100; + padding: 1rem; + background-color: #006100; } #back-button-link { - text-decoration: none; + text-decoration: none; } #back-button-container { - margin-left: 0.5rem; - margin-right: 0.5rem; + margin-left: 0.5rem; + margin-right: 0.5rem; } /* editor-flexbox.tsx */ -.editor-textarea{ - height: auto; - min-height: 700px; - resize: none; +.editor-textarea { + height: auto; + min-height: 700px; + resize: none; } /* photo_input.tsx */ .input-card { - page-break-before: always; - margin-bottom: 1rem; + page-break-before: always; + margin-bottom: 1rem; } /*also in radio.tsx*/ /* stylizes the hidden input for photo upload*/ .photo-upload-input { - display: none; + display: none; } /* photo.tsx */ .photo-card { - break-inside: avoid; - margin-bottom: 1rem; - margin-top: 1rem; + break-inside: avoid; + margin-bottom: 1rem; + margin-top: 1rem; +} + +.photo-card > .card-body > div { + margin-bottom: 1rem; } .photo-row { - display: grid; - grid-template-columns: 1fr 1fr; /* Forces two columns */ - gap: 1rem; + display: grid; + grid-template-columns: 1fr 1fr; /* Forces two columns */ + gap: 1rem; +} + +.photo-notes h3 { + font-weight: bold; } /* stylizes tab component here to avoid putting styles in editor*/ .tab-content { - margin-top: 1rem; + margin-top: 1rem; } /* styling for PDFRenderer component */ -.react-pdf__Page__textContent{ +.react-pdf__Page__textContent { display: none; - } +} /* 'hint' styling - the input components */ -.form-text{ - margin-left:.50rem; - margin-top:.25rem; - font-size:.875rem; - color:var(--bs-secondary-color); - font-style: italic; +.form-text { + margin-left: 0.5rem; + margin-top: 0.25rem; + font-size: 0.875rem; + color: var(--bs-secondary-color); + font-style: italic; } /* label styling for the Radio component */ .custom-label { - color: gray; - font-size: .9rem; + color: gray; + font-size: 0.9rem; } - /* BETA version text - styling for new templates - temperary */ .beta-text { - color: red; - padding-left: .75rem; - font-size: 0.9rem; + color: red; + padding-left: 0.75rem; + font-size: 0.9rem; } /* used in home.tsx */ .padding { - padding: 5px 10px 5px 10px; /* top right bottom left*/ + padding: 5px 10px 5px 10px; /* top right bottom left*/ } -.top-bottom-padding -{ - padding: 5px 0 5px 0; +.top-bottom-padding { + padding: 5px 0 5px 0; } /* disclaimer text */ .disclaimer-text { - padding-left: .75rem; - padding-right: .75rem; - font-size: 0.9rem; - font-weight: bold; + padding-left: 0.75rem; + padding-right: 0.75rem; + font-size: 0.9rem; + font-weight: bold; } .combustion_tests { - padding: 5px 10px 5px 10px; - background-color: white; + padding: 5px 10px 5px 10px; + background-color: white; } .align_justify { - justify-content: center; - text-align: justify; + justify-content: center; + text-align: justify; } .margin-left { - margin-left: 2em + margin-left: 2em; } - /* Primary Button - with background color */ .btn-primary { - color: #fff; /* Text color */ - background-color: #006100; /* Base background color */ - border: 2px solid #006100; - cursor: pointer; - text-align: center; - text-decoration: none; - display: inline-block; - transition: background-color 0.3s ease, border-color 0.3s ease; - outline: none; -} - -.btn-primary:hover, -.btn-primary:active, -.btn-primary:focus, -.btn-primary:focus-visible, + color: #fff; /* Text color */ + background-color: #006100; /* Base background color */ + border: 2px solid #006100; + cursor: pointer; + text-align: center; + text-decoration: none; + display: inline-block; + transition: + background-color 0.3s ease, + border-color 0.3s ease; + outline: none; +} + +.btn-primary:hover, +.btn-primary:active, +.btn-primary:focus, +.btn-primary:focus-visible, .btn-primary:focus-within { - background-color: #004d00; - border-color: #004d00; + background-color: #004d00; + border-color: #004d00; } .btn-primary:disabled { - background-color: #4caf50; - border-color: #4caf50; - cursor: not-allowed; /* Not-allowed cursor on disabled */ - opacity: 0.6; + background-color: #4caf50; + border-color: #4caf50; + cursor: not-allowed; /* Not-allowed cursor on disabled */ + opacity: 0.6; } /* Outline Primary Button - only outline */ .btn-outline-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, .show > .btn-primary.dropdown-toggle { - color: #fff; - background-color: #005f00; - border-color: #005f00; + color: #fff; + background-color: #005f00; + border-color: #005f00; } .btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-primary.dropdown-toggle:focus { - box-shadow: none + box-shadow: none; } -.btn-outline-primary{ - background-color: transparent; - cursor: pointer; - border: 2px solid #006100; - color: #006100; - +.btn-outline-primary { + background-color: transparent; + cursor: pointer; + border: 2px solid #006100; + color: #006100; } -.btn-outline-primary:hover{ - background-color: transparent; - color: #006100; - border-color: #006100; +.btn-outline-primary:hover { + background-color: transparent; + color: #006100; + border-color: #006100; } - /* Light Button - for Icons */ .btn-light { - background-color: transparent; - border-color: transparent; - color: #000000; + background-color: transparent; + border-color: transparent; + color: #000000; } btn-light:active, btn-light:hover { - box-shadow: none; + box-shadow: none; } .welcome-header { - padding-left: 1rem; - padding-right: 1rem; - color: #006100; - font-size: 28px; - font-weight: bold; + padding-left: 1rem; + padding-right: 1rem; + color: #006100; + font-size: 28px; + font-weight: bold; } .welcome-content { - padding-left: 1.5rem; - padding-right: 1.5rem; - color: #000000; - font-size: 14px; - font-weight: bold; -} - -@media (max-width: 480px) { - .welcome-content { padding-left: 1.5rem; padding-right: 1.5rem; color: #000000; - font-size: 12px; + font-size: 14px; font-weight: bold; - } } +@media (max-width: 480px) { + .welcome-content { + padding-left: 1.5rem; + padding-right: 1.5rem; + color: #000000; + font-size: 12px; + font-weight: bold; + } +} .loader { - border: 8px solid #f3f3f3; /* Light gray */ - border-top: 8px solid #3498db; /* Blue */ - border-radius: 50%; - width: 30px; /* Size of the loader */ - height: 30px; /* Size of the loader */ - animation: spin 1s linear infinite; /* Animation */ - background-color: white; + border: 8px solid #f3f3f3; /* Light gray */ + border-top: 8px solid #3498db; /* Blue */ + border-radius: 50%; + width: 30px; /* Size of the loader */ + height: 30px; /* Size of the loader */ + animation: spin 1s linear infinite; /* Animation */ + background-color: white; } @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } - /* used in photo-input and photo component */ .photo-container { - width: auto; /* Allow width to adjust based on content */ - max-width: 500px; /* Maximum width */ - overflow: visible; /* Hide overflow */ - height: auto; /* Allow height to adjust based on content */ - max-height: 600px; /* Maximum height */ - margin-bottom: 5px; /* Margin at the bottom */ - position: relative; /* Relative positioning for overlay elements */ - display: inline-block; /* Display inline-block to shrink to content size */ + width: auto; /* Allow width to adjust based on content */ + max-width: 500px; /* Maximum width */ + overflow: visible; /* Hide overflow */ + height: auto; /* Allow height to adjust based on content */ + max-height: 600px; /* Maximum height */ + margin-bottom: 5px; /* Margin at the bottom */ + position: relative; /* Relative positioning for overlay elements */ + display: inline-block; /* Display inline-block to shrink to content size */ } .photo-report-container { - width: auto; /* Allow width to adjust based on content */ - max-width: 500px; /* Maximum width */ - overflow: hidden; /* Hide overflow */ - height: auto; /* Allow height to adjust based on content */ - max-height: 600px; /* Maximum height */ - position: relative; /* Relative positioning for overlay elements */ - display: inline-block; /* Display inline-block to shrink to content size */ + width: auto; /* Allow width to adjust based on content */ + max-width: 500px; /* Maximum width */ + overflow: hidden; /* Hide overflow */ + height: auto; /* Allow height to adjust based on content */ + max-height: 600px; /* Maximum height */ + position: relative; /* Relative positioning for overlay elements */ + display: inline-block; /* Display inline-block to shrink to content size */ } /* used in photo-input component */ .photo-delete-button { - position: absolute; - top: 15px; - right: 15px; - border-radius: 50%; - padding: 5px; - z-index: 5; /* Ensures the button stays on top */ - cursor: pointer; - transform: scale(1.2); /* Increase icon size */ - stroke-width: 2; + position: absolute; + top: 15px; + right: 15px; + border-radius: 50%; + padding: 5px; + z-index: 5; /* Ensures the button stays on top */ + cursor: pointer; + transform: scale(1.2); /* Increase icon size */ + stroke-width: 2; } /* used in photo-input component */ -.image-tag { - width: '400px'; /* Fixed width */ - height: '400px'; /* Fixed height */ - object-fit: 'cover'; /* Ensure the image covers the area (cropped if necessary) */ - border-radius: '8px'; /*Optional: adds rounded corners to the image */ +.image-tag { + width: '400px'; /* Fixed width */ + height: '400px'; /* Fixed height */ + object-fit: 'cover'; /* Ensure the image covers the area (cropped if necessary) */ + border-radius: '8px'; /*Optional: adds rounded corners to the image */ } /* used in photo-input component */ @@ -451,41 +452,40 @@ btn-light:hover { /* used in photo-input component */ .custom-modal { - max-width: 300px; - margin: auto; + max-width: 300px; + margin: auto; } /* Default font size */ .modal-body-text { - font-size: 18px; /* Adjust the default font size */ + font-size: 18px; /* Adjust the default font size */ } /* Common font size for medium, small, and extra small devices */ @media (max-width: 991.98px) { - .modal-body-text { - font-size: 14px; /* Adjust font size for all specified devices */ - } + .modal-body-text { + font-size: 14px; /* Adjust font size for all specified devices */ + } } .safari-print-header { -visibility: hidden;} + visibility: hidden; +} @media print { - /* Adjusts the margin for printing */ - @page { - margin-top: 4rem; - margin-bottom: 4rem; - } - - - .safari-print-header { - text-align: center; - font-size: 10px; - font-weight: bold; - visibility: visible; - margin-bottom: 10px; - display: block; - page-break-before: always; - } - + /* Adjusts the margin for printing */ + @page { + margin-top: 4rem; + margin-bottom: 4rem; + } + + .safari-print-header { + text-align: center; + font-size: 10px; + font-weight: bold; + visibility: visible; + margin-bottom: 10px; + display: block; + page-break-before: always; + } } diff --git a/src/components/photo.tsx b/src/components/photo.tsx index cb8a89a2..1e9a9233 100644 --- a/src/components/photo.tsx +++ b/src/components/photo.tsx @@ -5,12 +5,14 @@ import { Card, Image, Row, Col } from 'react-bootstrap' import DateTimeStr from './date_time_str' import GpsCoordStr from './gps_coord_str' import type PhotoMetadata from '../types/photo_metadata.type' +import JSONValue from '../types/json_value.type' interface PhotoProps { description: React.ReactNode label: string photos: { id: string; photo: Blob; metadata: PhotoMetadata }[] // Array of photo objects with metadata required: boolean + noteValue: JSONValue } /** @@ -30,13 +32,24 @@ interface PhotoProps { * @param required When unset, the Photo component will only show if there is a * photo attachment in the data store with the given id. When set, the Photo component * will always show and the Photo component will indicate when the photo is missing. + * @param noteValue The value to be displayed as the note for the photos */ -const Photo: FC = ({ description, label, photos, required }) => { +const Photo: FC = ({ + description, + label, + photos, + required, + noteValue, +}) => { return (photos && photos.length > 0) || required ? ( {label} {description} +
+

Notes:

+
{noteValue ? noteValue.toString() : null}
+
{photos && photos.length > 0 ? Array.isArray(photos) && ( diff --git a/src/components/photo_wrapper.tsx b/src/components/photo_wrapper.tsx index e1388198..400c238d 100644 --- a/src/components/photo_wrapper.tsx +++ b/src/components/photo_wrapper.tsx @@ -4,6 +4,8 @@ import { StoreContext } from './store' import Photo from './photo' import { useDB } from '../utilities/database_utils' +import JSONValue from '../types/json_value.type' + interface PhotoWrapperProps { children: React.ReactNode id: string @@ -14,6 +16,23 @@ interface PhotoWrapperProps { fromParent?: boolean } +interface JSONObject { + [key: string]: JSONValue +} +//This function takes a data object from the StoreContext and converts it into an object +//whose values we can access via string-type keys +function convertDataObject(data: {}): JSONObject { + let jsonObject = data as JSONObject + let newDataObject: JSONObject = {} + + for (const key in jsonObject) { + if (jsonObject.hasOwnProperty(key)) { + newDataObject[key] = jsonObject[key] + } + } + return newDataObject +} + /** * A component that wraps a Photo component in order to tie it to the data store * @@ -125,18 +144,20 @@ const PhotoWrapper: FC = ({ return ( {({ attachments, data }) => { - return ( - - ) + if (data) + return ( + + ) }} ) From dd64e5ca3d44141ca48738a5944043f61d74811d Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Wed, 18 Dec 2024 15:46:54 -0700 Subject: [PATCH 08/16] 74 : Added conditional logic so that we can turn notes section on/off in the .mdx file and the notes section won't render in the report unless it exists. --- src/components/photo.tsx | 11 +++++++---- src/components/photo_input.tsx | 24 +++++++++++++++--------- src/components/photo_input_wrapper.tsx | 4 ++++ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/components/photo.tsx b/src/components/photo.tsx index 1e9a9233..29cecd67 100644 --- a/src/components/photo.tsx +++ b/src/components/photo.tsx @@ -46,10 +46,13 @@ const Photo: FC = ({ {label} {description} -
-

Notes:

-
{noteValue ? noteValue.toString() : null}
-
+ {noteValue && ( +
+

Notes:

+
{noteValue ? noteValue.toString() : null}
+
+ )} + {photos && photos.length > 0 ? Array.isArray(photos) && ( diff --git a/src/components/photo_input.tsx b/src/components/photo_input.tsx index 33e1d050..25a65e5b 100644 --- a/src/components/photo_input.tsx +++ b/src/components/photo_input.tsx @@ -26,6 +26,7 @@ interface PhotoInputProps { noteValue: JSONValue | undefined updateNoteValue: (value: string) => void id: string + notes?: boolean } // TODO: Determine whether or not the useEffect() method is needed. // We don't seem to need a separate camera button on an Android phone. @@ -47,6 +48,7 @@ interface PhotoInputProps { * @param noteValue The value to populate the photo caption box * @param updateNoteValue Function for setting the note in the photo caption box * @param id Attachment id + * @param notes If notes is false, then the note input will not show up */ const PhotoInput: FC = ({ children, @@ -61,6 +63,7 @@ const PhotoInput: FC = ({ noteValue, updateNoteValue, id, + notes = true, }) => { // Create references to the hidden file inputs const hiddenPhotoCaptureInputRef = useRef(null) @@ -250,15 +253,18 @@ const PhotoInput: FC = ({
)} - + {notes && ( + + )} + = ({ children, @@ -51,6 +53,7 @@ const PhotoInputWrapper: FC = ({ label, uploadable, count = 10, + notes, }) => { const [loading, setLoading] = useState(false) // Loading state const [error, setError] = useState('') // Loading state @@ -234,6 +237,7 @@ const PhotoInputWrapper: FC = ({ } noteValue={convertDataObject(data)[`${id}_note`]} id={id} + notes={notes} > {children} From fc404076dc881a2f5afda954704545798a2ddd59 Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Wed, 18 Dec 2024 15:52:12 -0700 Subject: [PATCH 09/16] 74 : Remove notes section for building number photo prompt. --- .../reusable/project_info_inputs.mdx | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/templates/reusable/project_info_inputs.mdx b/src/templates/reusable/project_info_inputs.mdx index 13485c49..e8e66d79 100644 --- a/src/templates/reusable/project_info_inputs.mdx +++ b/src/templates/reusable/project_info_inputs.mdx @@ -1,10 +1,8 @@ - #### New Project Information - - -##### Installer Information -*The Installer information is optional, but we recomment filling in at least one -field for reference in the the final report.* + + +##### Installer Information *The Installer information is optional, but we +recomment filling in at least one field for reference in the the final report.* @@ -12,20 +10,18 @@ field for reference in the the final report.* ##### Project Address + - - Provide a photo of the building that shows the building number. + + Provide a photo of the building that shows the building number. - - - - - - - \ No newline at end of file From 6ebaf84843e0ff7d1d4907bf1c1ebcd9e0ef9e9e Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Wed, 8 Jan 2025 08:54:41 -0700 Subject: [PATCH 10/16] 74: Replaced TextInput with TextInputWrapper. --- src/components/photo_input.tsx | 13 +++---------- src/components/photo_input_wrapper.tsx | 6 ------ 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/components/photo_input.tsx b/src/components/photo_input.tsx index 25a65e5b..40608dc2 100644 --- a/src/components/photo_input.tsx +++ b/src/components/photo_input.tsx @@ -10,6 +10,7 @@ import type PhotoMetaData from '../types/photo_metadata.type' import { PHOTO_MIME_TYPES } from '../utilities/photo_utils' import { TfiTrash } from 'react-icons/tfi' import TextInput from './text_input' +import TextInputWrapper from './text_input_wrapper' import JSONValue from '../types/json_value.type' interface PhotoInputProps { @@ -23,8 +24,6 @@ interface PhotoInputProps { loading: boolean error: string count: number - noteValue: JSONValue | undefined - updateNoteValue: (value: string) => void id: string notes?: boolean } @@ -45,8 +44,6 @@ interface PhotoInputProps { * @param uploadable When set, the PhotoInput component will open the gallery to upload the photo. * When unset, the PhotoInput component will use device camera for taking new photo (default). * @param loader When set, a loading image will be displayed during the upload process. - * @param noteValue The value to populate the photo caption box - * @param updateNoteValue Function for setting the note in the photo caption box * @param id Attachment id * @param notes If notes is false, then the note input will not show up */ @@ -60,8 +57,6 @@ const PhotoInput: FC = ({ loading, error, count, - noteValue, - updateNoteValue, id, notes = true, }) => { @@ -254,11 +249,9 @@ const PhotoInput: FC = ({ )} {notes && ( - = ({ attachments, upsertAttachment, deleteAttachment, - data, - upsertData, }) => { const deletePhoto = (photoId: string) => { deleteAttachment(photoId) @@ -232,10 +230,6 @@ const PhotoInputWrapper: FC = ({ loading={loading} error={error} count={count} - updateNoteValue={(value: any) => - upsertData(`${id}_note`, value) - } - noteValue={convertDataObject(data)[`${id}_note`]} id={id} notes={notes} > From 979e0c344c34486e4b62f5660b56138e6500b4c1 Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Wed, 8 Jan 2025 09:32:40 -0700 Subject: [PATCH 11/16] 74: Cleaned up code. --- src/components/photo_input.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/photo_input.tsx b/src/components/photo_input.tsx index 40608dc2..805e0120 100644 --- a/src/components/photo_input.tsx +++ b/src/components/photo_input.tsx @@ -9,9 +9,7 @@ import GpsCoordStr from './gps_coord_str' import type PhotoMetaData from '../types/photo_metadata.type' import { PHOTO_MIME_TYPES } from '../utilities/photo_utils' import { TfiTrash } from 'react-icons/tfi' -import TextInput from './text_input' import TextInputWrapper from './text_input_wrapper' -import JSONValue from '../types/json_value.type' interface PhotoInputProps { children: React.ReactNode From dbca760525ffea41a24dd2a221e9a51935e2a6f4 Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Wed, 8 Jan 2025 11:58:11 -0700 Subject: [PATCH 12/16] 74 :Deleted the JSON gymnastics I was doing and replaced with the get() function from lodash. Cleaned up code a bit. --- src/components/photo.tsx | 7 ++--- src/components/photo_input_wrapper.tsx | 22 ++------------ src/components/photo_wrapper.tsx | 29 ++++--------------- .../reusable/project_info_inputs.mdx | 2 +- 4 files changed, 11 insertions(+), 49 deletions(-) diff --git a/src/components/photo.tsx b/src/components/photo.tsx index 29cecd67..7a4e170b 100644 --- a/src/components/photo.tsx +++ b/src/components/photo.tsx @@ -5,16 +5,14 @@ import { Card, Image, Row, Col } from 'react-bootstrap' import DateTimeStr from './date_time_str' import GpsCoordStr from './gps_coord_str' import type PhotoMetadata from '../types/photo_metadata.type' -import JSONValue from '../types/json_value.type' interface PhotoProps { description: React.ReactNode label: string photos: { id: string; photo: Blob; metadata: PhotoMetadata }[] // Array of photo objects with metadata required: boolean - noteValue: JSONValue + noteValue: string | undefined } - /** * A component that displays a photo, timestamp, geolocation, label, and description * Displays multiple photos (2 per row) with metadata in a grid layout @@ -22,7 +20,6 @@ interface PhotoProps { * @param description Content (most commonly markdown text) used to describe the photo * @param label Label for the component * @param metadata Photo metadata including timestamp and geolocation - * @param notes User notes associated with the photo * @param photos Array of photo objects with photo Blob and metadata for each photo * { * id attachment id for the photo and metadata @@ -49,7 +46,7 @@ const Photo: FC = ({ {noteValue && (

Notes:

-
{noteValue ? noteValue.toString() : null}
+
{noteValue ? noteValue : null}
)} diff --git a/src/components/photo_input_wrapper.tsx b/src/components/photo_input_wrapper.tsx index f148754d..17635c46 100644 --- a/src/components/photo_input_wrapper.tsx +++ b/src/components/photo_input_wrapper.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useState } from 'react' +import React, { FC, useState } from 'react' import imageCompression from 'browser-image-compression' @@ -7,7 +7,6 @@ import PhotoInput from './photo_input' import PhotoMetadata from '../types/photo_metadata.type' import { getMetadataFromPhoto, photoProperties } from '../utilities/photo_utils' -import JSONValue from '../types/json_value.type' interface PhotoInputWrapperProps { children: React.ReactNode @@ -18,23 +17,6 @@ interface PhotoInputWrapperProps { notes?: boolean } -interface JSONObject { - [key: string]: JSONValue -} -//This function takes a data object from the StoreContext and converts it into an object -//whose values we can access via string-type keys -function convertDataObject(data: {}): JSONObject { - let jsonObject = data as JSONObject - let newDataObject: JSONObject = {} - - for (const key in jsonObject) { - if (jsonObject.hasOwnProperty(key)) { - newDataObject[key] = jsonObject[key] - } - } - return newDataObject -} - /** * A component that wraps a PhotoInput component in order to tie it to the data store * @@ -45,7 +27,7 @@ function convertDataObject(data: {}): JSONObject { * @param label The label of the PhotoInput component * @param uploadable When set, the PhotoInput component will open the gallery to upload the photo. * When unset, the PhotoInput component will use device camera for taking new photo. - * @param note Boolean from the mdx component that indicates whether the notes field will be available + * @param notes Boolean from the mdx component that indicates whether the notes field will be available */ const PhotoInputWrapper: FC = ({ children, diff --git a/src/components/photo_wrapper.tsx b/src/components/photo_wrapper.tsx index 09f7ea54..0b26466c 100644 --- a/src/components/photo_wrapper.tsx +++ b/src/components/photo_wrapper.tsx @@ -1,11 +1,9 @@ import React, { FC, useEffect, useState } from 'react' - +import { get } from 'lodash' import { StoreContext } from './store' import Photo from './photo' import { useDB } from '../utilities/database_utils' -import JSONValue from '../types/json_value.type' - interface PhotoWrapperProps { children: React.ReactNode id: string @@ -15,23 +13,6 @@ interface PhotoWrapperProps { parent?: any } -interface JSONObject { - [key: string]: JSONValue -} -//This function takes a data object from the StoreContext and converts it into an object -//whose values we can access via string-type keys -function convertDataObject(data: {}): JSONObject { - let jsonObject = data as JSONObject - let newDataObject: JSONObject = {} - - for (const key in jsonObject) { - if (jsonObject.hasOwnProperty(key)) { - newDataObject[key] = jsonObject[key] - } - } - return newDataObject -} - /** * A component that wraps a Photo component in order to tie it to the data store * @@ -54,7 +35,6 @@ const PhotoWrapper: FC = ({ parent, }) => { const [matchingAttachments, setMatchingAttachments] = useState({}) - const [projectDoc, setProjectDoc] = useState(parent) const db = useDB() useEffect(() => { @@ -107,7 +87,6 @@ const PhotoWrapper: FC = ({ setMatchingAttachments(matchingAttachments) // Set the filtered attachments in state - setProjectDoc(doc) // Set the full document if needed }) .catch((err: any) => { console.error('Failed to get matching attachments:', err) @@ -153,7 +132,11 @@ const PhotoWrapper: FC = ({ : getMatchingAttachments(attachments, id) } required={required} - noteValue={convertDataObject(data)[`${id}_note`]} + noteValue={ + get(data, `${id}_note`) + ? get(data, `${id}_note`) + : '' + } /> ) }} diff --git a/src/templates/reusable/project_info_inputs.mdx b/src/templates/reusable/project_info_inputs.mdx index e8e66d79..d2cbd86a 100644 --- a/src/templates/reusable/project_info_inputs.mdx +++ b/src/templates/reusable/project_info_inputs.mdx @@ -2,7 +2,7 @@ ##### Installer Information *The Installer information is optional, but we -recomment filling in at least one field for reference in the the final report.* +recommend filling in at least one field for reference in the the final report.* From 9d1a62a7ba99441c687e77e034bbdc1ce032d2f9 Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Fri, 17 Jan 2025 10:57:41 -0700 Subject: [PATCH 13/16] 74: Notes with multiple lines now show up on multiple lines in the report and they show up below the photo. --- src/App.css | 4 ++++ src/components/photo.tsx | 22 +++++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/App.css b/src/App.css index 159dc31c..612453c1 100644 --- a/src/App.css +++ b/src/App.css @@ -220,6 +220,10 @@ h1 + h3 { font-weight: bold; } +.photo-notes .photo-note-string { + margin-bottom: unset; +} + /* stylizes tab component here to avoid putting styles in editor*/ .tab-content { margin-top: 1rem; diff --git a/src/components/photo.tsx b/src/components/photo.tsx index 7a4e170b..9e4818c4 100644 --- a/src/components/photo.tsx +++ b/src/components/photo.tsx @@ -38,18 +38,12 @@ const Photo: FC = ({ required, noteValue, }) => { + const noteValueArray = noteValue?.split(`\n`) return (photos && photos.length > 0) || required ? ( {label} {description} - {noteValue && ( -
-

Notes:

-
{noteValue ? noteValue : null}
-
- )} - {photos && photos.length > 0 ? Array.isArray(photos) && ( @@ -111,6 +105,20 @@ const Photo: FC = ({ ) : required && Missing Photo} + {noteValue && ( +
+

Notes:

+
+ {noteValueArray + ? noteValueArray.map(string => ( +

+ {string} +

+ )) + : null} +
+
+ )}
) : null From f5e392e3a2ec85df8286bb75b232d0379232e0ec Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Fri, 17 Jan 2025 11:17:35 -0700 Subject: [PATCH 14/16] 74: Made the textarea get taller on user click. --- src/components/text_input.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/components/text_input.tsx b/src/components/text_input.tsx index d0c6a31f..941e0ea9 100644 --- a/src/components/text_input.tsx +++ b/src/components/text_input.tsx @@ -33,6 +33,11 @@ const TextInput: FC = ({ max, regexp, }) => { + const booststrapFloatingLabelHeight = + 'calc(3.5rem + calc(var(--bs-border-width)* 2))' + const [textAreaHeight, setTextAreaHeight] = useState( + booststrapFloatingLabelHeight, + ) const [error, setError] = useState('') const handleChange = (inputValue: string) => { @@ -50,6 +55,14 @@ const TextInput: FC = ({ } } + const handleFocus = () => { + setTextAreaHeight('100px') + } + + const handleBlur = () => { + setTextAreaHeight(booststrapFloatingLabelHeight) + } + return ( <> @@ -59,6 +72,8 @@ const TextInput: FC = ({ placeholder="A placeholder" value={value || ''} isInvalid={Boolean(error)} + onFocus={handleFocus} + onBlur={handleBlur} /> {error && ( From 659ea54dbce082dbaee3a9680c9364d29a595959 Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Mon, 20 Jan 2025 09:19:58 -0700 Subject: [PATCH 15/16] 74 : First stab at adding classes that get the textarea and label to behave the way we want. Working but need to test more and clean up code. --- src/App.css | 13 +++++++++ src/components/text_input.tsx | 55 ++++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/App.css b/src/App.css index 612453c1..eb08d787 100644 --- a/src/App.css +++ b/src/App.css @@ -460,6 +460,19 @@ btn-light:hover { margin: auto; } +/* Used in text_input component */ +.label-hidden.form-floating > label { + visibility: hidden; +} + +.label-hidden.form-floating > textarea { + padding: 1rem 0.75rem !important; +} + +.text-area-expanded.form-floating > textarea { + height: 100px; +} + /* Default font size */ .modal-body-text { font-size: 18px; /* Adjust the default font size */ diff --git a/src/components/text_input.tsx b/src/components/text_input.tsx index 941e0ea9..22300ea0 100644 --- a/src/components/text_input.tsx +++ b/src/components/text_input.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from 'react' +import { FC, useEffect, useState } from 'react' import FloatingLabel from 'react-bootstrap/FloatingLabel' import Form from 'react-bootstrap/Form' @@ -33,11 +33,18 @@ const TextInput: FC = ({ max, regexp, }) => { - const booststrapFloatingLabelHeight = - 'calc(3.5rem + calc(var(--bs-border-width)* 2))' - const [textAreaHeight, setTextAreaHeight] = useState( - booststrapFloatingLabelHeight, + const bootStrapTextAreaStyles = { + height: 'calc(3.5rem + calc(var(--bs-border-width)* 2))', + } + const bootstrapFloatingLabelClasses = 'mb-3' + const [textAreaStyles, setTextAreaStyles] = useState( + bootStrapTextAreaStyles, ) + const [floatingLabelClasses, setFloatingLabelClasses] = + useState('mb-3') + + const [textAreaFocused, setTextAreaFocused] = useState(false) + const [error, setError] = useState('') const handleChange = (inputValue: string) => { @@ -56,24 +63,52 @@ const TextInput: FC = ({ } const handleFocus = () => { - setTextAreaHeight('100px') + // setTextAreaStyles({ + // height: '100px', + // paddingTop: value ? '.625rem' : undefined, + // backgroundColor: 'pink', + // }) + // setHintClasses('mb-3 floating-label-hidden') } const handleBlur = () => { - setTextAreaHeight(booststrapFloatingLabelHeight) + // setTextAreaStyles({ + // height: undefined, + // paddingTop: value ? '.625rem' : undefined, + // backgroundColor: 'pink', + // }) + // setHintClasses( + // value + // ? 'mb-3 floating-label-hidden' + // : bootstrapFloatingLabelClasses, + // ) } + useEffect(() => { + let floatingLabelClasses = 'mb-3' + if (value || textAreaFocused) { + floatingLabelClasses += ' label-hidden' + } + + if (textAreaFocused) { + floatingLabelClasses += ' text-area-expanded' + } + + setFloatingLabelClasses(floatingLabelClasses) + }, [value, textAreaFocused]) + return ( <> - + handleChange(event.target.value)} placeholder="A placeholder" value={value || ''} isInvalid={Boolean(error)} - onFocus={handleFocus} - onBlur={handleBlur} + onFocus={() => setTextAreaFocused(true)} + onBlur={() => setTextAreaFocused(false)} + // style={textAreaStyles} /> {error && ( From ab128d035181ec54d272e5b80720d23df2f27cbd Mon Sep 17 00:00:00 2001 From: Lindsay Johnston Date: Mon, 20 Jan 2025 11:07:25 -0700 Subject: [PATCH 16/16] 74 : Cleaned up code. --- src/components/text_input.tsx | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/components/text_input.tsx b/src/components/text_input.tsx index 22300ea0..832ab31f 100644 --- a/src/components/text_input.tsx +++ b/src/components/text_input.tsx @@ -33,13 +33,6 @@ const TextInput: FC = ({ max, regexp, }) => { - const bootStrapTextAreaStyles = { - height: 'calc(3.5rem + calc(var(--bs-border-width)* 2))', - } - const bootstrapFloatingLabelClasses = 'mb-3' - const [textAreaStyles, setTextAreaStyles] = useState( - bootStrapTextAreaStyles, - ) const [floatingLabelClasses, setFloatingLabelClasses] = useState('mb-3') @@ -62,28 +55,6 @@ const TextInput: FC = ({ } } - const handleFocus = () => { - // setTextAreaStyles({ - // height: '100px', - // paddingTop: value ? '.625rem' : undefined, - // backgroundColor: 'pink', - // }) - // setHintClasses('mb-3 floating-label-hidden') - } - - const handleBlur = () => { - // setTextAreaStyles({ - // height: undefined, - // paddingTop: value ? '.625rem' : undefined, - // backgroundColor: 'pink', - // }) - // setHintClasses( - // value - // ? 'mb-3 floating-label-hidden' - // : bootstrapFloatingLabelClasses, - // ) - } - useEffect(() => { let floatingLabelClasses = 'mb-3' if (value || textAreaFocused) { @@ -108,7 +79,6 @@ const TextInput: FC = ({ isInvalid={Boolean(error)} onFocus={() => setTextAreaFocused(true)} onBlur={() => setTextAreaFocused(false)} - // style={textAreaStyles} /> {error && (