diff --git a/frontend/src/components/Buttons/CLButtons.jsx b/frontend/src/components/Buttons/CLButtons.jsx index 036bdd4e..311ece25 100644 --- a/frontend/src/components/Buttons/CLButtons.jsx +++ b/frontend/src/components/Buttons/CLButtons.jsx @@ -1,8 +1,12 @@ -import "./CLButtons.css" +import "./CLButtons.css"; -const DEFAULT_HEIGHT = "54px" -const DEFAULT_WIDTH = "fit-content" -const DEFAULT_ON_CLICK = () => {} +const DEFAULT_PRIMARY_CLASSNAME = "primary-variant-button"; +const DEFAULT_SECONDARY_CLASSNAME = "secondary-variant-button"; + +const DEFAULT_HEIGHT = "54px"; +const DEFAULT_WIDTH = "fit-content"; +const DEFAULT_TYPE = "button"; +const DEFAULT_ON_CLICK = () => {}; /** * PRIMARY variant of the clinical logging button. @@ -11,24 +15,30 @@ const DEFAULT_ON_CLICK = () => {} */ export const CLButtonPrimary = ({ children, + className, height = DEFAULT_HEIGHT, width = DEFAULT_WIDTH, - onClick = DEFAULT_ON_CLICK + type = DEFAULT_TYPE, + onClick = DEFAULT_ON_CLICK, }) => { + const updatedClassName = className + ? DEFAULT_PRIMARY_CLASSNAME + " " + className + : DEFAULT_PRIMARY_CLASSNAME; return ( - - ) -} + ); +}; /** * SECONDARY variant of the clinical logging button. @@ -37,21 +47,27 @@ export const CLButtonPrimary = ({ */ export const CLButtonSecondary = ({ children, + className, height = DEFAULT_HEIGHT, width = DEFAULT_WIDTH, - onClick = DEFAULT_ON_CLICK + type = DEFAULT_TYPE, + onClick = DEFAULT_ON_CLICK, }) => { - + const updatedClassName = className + ? DEFAULT_SECONDARY_CLASSNAME + " " + className + : DEFAULT_SECONDARY_CLASSNAME; + return ( - - ) -} \ No newline at end of file + ); +}; diff --git a/frontend/src/components/NewLogModal/NewLogModal.css b/frontend/src/components/NewLogModal/NewLogModal.css new file mode 100644 index 00000000..b0ebfdda --- /dev/null +++ b/frontend/src/components/NewLogModal/NewLogModal.css @@ -0,0 +1,81 @@ +.modal-content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 2rem; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + height: 380px; + width: 640px; + background-color: #FBFCFE; + border-radius: 20px; + box-shadow: 24; + } + + .close-modal-button { + position: absolute; + top: 40px; + left: 40px; + height: 40px; + width: 40px; + display: flex; + justify-content: center; + align-items: center; + background-color: transparent; + color: #244B94; + border-radius: 40px; + } + + .close-modal-button:hover { + background-color: rgb(219, 219, 219); + } + + .modal-description { + font-size: 24px; + font-weight: bold; + margin: 1rem; + } + + .new-log-modal-divider { + width: 60%; + } + + .new-log-modal-buttons-container { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 2rem; + gap: 20px; + } + + .upload-photo-button { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + color: #F7FAFF; + gap: 12px; + } + +.create-manually-button { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + color: #4f607e; + gap: 12px; +} + +.close-x-icon { + height: 34px; + width: 34px; + color: inherit; +} + +.modal-icon { + height: 40px; + width: 40px; +} diff --git a/frontend/src/components/NewLogModal/NewLogModal.jsx b/frontend/src/components/NewLogModal/NewLogModal.jsx new file mode 100644 index 00000000..a164cec1 --- /dev/null +++ b/frontend/src/components/NewLogModal/NewLogModal.jsx @@ -0,0 +1,65 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { CLButtonPrimary, CLButtonSecondary } from "../Buttons/CLButtons"; +import { + PhotoIcon, + PencilSquareIcon, + XMarkIcon, +} from "@heroicons/react/24/outline"; +import Box from "@mui/material/Box"; +import Modal from "@mui/material/Modal"; +import Divider from "@mui/material/Divider"; +import "./NewLogModal.css"; + +export const NewLogModal = () => { + const [open, setOpen] = useState(false); + const handleOpen = () => setOpen(true); + const handleClose = () => setOpen(false); + + const navigate = useNavigate(); + + const handleUploadPhoto = () => { + navigate("/upload-photo"); + }; + + const handleCreateManually = () => { + navigate("/manualEntry"); + }; + + return ( +
+ + Create New Log + + + + +

+ How would you like to create a new log? +

+ +
+ + +

Upload Photo

+
+ + +

Create Manually

+
+
+
+
+
+ ); +}; diff --git a/frontend/src/components/UploadPhoto/PreviewSection.css b/frontend/src/components/UploadPhoto/PreviewSection.css new file mode 100644 index 00000000..4ee4c76b --- /dev/null +++ b/frontend/src/components/UploadPhoto/PreviewSection.css @@ -0,0 +1,170 @@ +.preview-section { + position: fixed; + top: 15%; + right: 2%; + width: 400px; + height: 75%; + border-radius: 10px; + background: #FAF9F9; + box-shadow: 8px 0 30px rgba(0, 0, 0, 0.15); + padding: 2rem; + z-index: 1000; + display: flex; + flex-direction: column; + } + + .preview-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid #ECEBED; + } + + .preview-title { + display: flex; + align-items: center; + gap: 0.3rem; + color: #333333; + font-weight: 600; + background: none; + } + + .preview-section-icon { + color: #2B4B96; + width: 20px; + height: 20px; + } + + /* Edit Button */ + .edit-button { + display: flex; + align-items: center; + gap: 0.3rem; + border: 1px solid #9AB0E1; + border-radius: 15px; + background: none; + color: #333333; + cursor: pointer; + transition: color 0.2s ease; + } + + .edit-button.active { + color: #2B4B96; + } + + .edit-icon { + width: 16px; + height: 16px; + } + + /* Delete Button */ + .delete-button { + position: absolute; + top: 1rem; + right: 1.5rem; + width: 24px; + height: 24px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.5); + border: none; + color: white; + font-size: 18px; + line-height: 1; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s ease; + } + + .delete-button:hover { + background: rgba(0, 0, 0, 0.7); + } + + /* Preview List */ + .preview-list { + flex-grow: 1; + overflow-y: auto; + overflow-x: hidden; + border-radius: 20px; + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; + margin-bottom: 0.5rem; + } + + .preview-list::-webkit-scrollbar { + width: 6px; + } + + .preview-list::-webkit-scrollbar-track { + background: #F3F4F6; + border-radius: 3px; + } + + .preview-list::-webkit-scrollbar-thumb { + background: #D1D5DB; + border-radius: 3px; + } + + .preview-list::-webkit-scrollbar-thumb:hover { + background: #9CA3AF; + } + + /* Preview Item */ + .preview-item { + width: 100%; + padding: 0.5rem; + position: relative; + } + + .preview-image { + width: calc(100% - 8px); + border-radius: 12px; + object-fit: contain; + max-height: 500px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } + + /* Preview Footer */ + .preview-footer { + margin-top: auto; + display: flex; + justify-content: flex-end; + } + + .preview-footer .transcribe-button { + margin-bottom: -1rem; + border-radius: 20px; + padding: 10px 14px; + font-size: 14px; + } + + /* Transcribe Button */ + .transcribe-button { + background-color: #244B94; + color: white; + border: none; + border-radius: 15px; + padding: 12px 20px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s ease; + } + + .transcribe-button:hover { + background-color: #1e3a7b; + } + + .transcribe-button:active { + background-color: #152c5f; + } + + .transcribe-button:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(43, 75, 150, 0.4); + } \ No newline at end of file diff --git a/frontend/src/components/UploadPhoto/PreviewSection.jsx b/frontend/src/components/UploadPhoto/PreviewSection.jsx new file mode 100644 index 00000000..6b53b517 --- /dev/null +++ b/frontend/src/components/UploadPhoto/PreviewSection.jsx @@ -0,0 +1,79 @@ +import React, { useState } from "react"; +import { + ChevronDoubleRightIcon, + PencilSquareIcon, +} from "@heroicons/react/24/solid"; +import "./PreviewSection.css"; + +export default function PreviewSection({ + files, + setFiles, + handlePreviewClick, + handleTranscribe, +}) { + const [isEditing, setIsEditing] = useState(false); + + /** Toggle edit mode */ + const handleEdit = () => { + setIsEditing((prev) => !prev); + }; + + /** Handle image deletion */ + const handleDeleteImage = (timestampToDelete) => { + setFiles((prevFiles) => { + // Find the file to delete and revoke its URL + const fileToDelete = prevFiles.find( + (file) => file.timestamp === timestampToDelete + ); + if (fileToDelete?.preview) { + URL.revokeObjectURL(fileToDelete.preview); + } + // Filter out the deleted file + return prevFiles.filter((file) => file.timestamp !== timestampToDelete); + }); + }; + + return ( +
+
+ + +
+ +
+ {files.map((fileData, index) => ( +
+ {`Preview + {isEditing && ( + + )} +
+ ))} +
+ +
+ +
+
+ ); +} diff --git a/frontend/src/components/UploadPhoto/UploadArea.css b/frontend/src/components/UploadPhoto/UploadArea.css new file mode 100644 index 00000000..79c0c81d --- /dev/null +++ b/frontend/src/components/UploadPhoto/UploadArea.css @@ -0,0 +1,86 @@ +.upload-area { + width: 45%; + margin-left: 23%; + aspect-ratio: 3/2; + border: 2px dashed rgba(69, 72, 76, 0.3); + border-radius: 30px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + } + + .upload-area.drag-active { + border-color: #2B4B96; + background-color: rgba(43, 75, 150, 0.05); + } + + .file-input { + opacity: 0; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + cursor: pointer; + } + + /* Upload Label */ + .upload-label { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + pointer-events: none; + } + + .upload-icon { + width: 80px; + height: 80px; + background: #333333; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 0 20px rgba(30, 55, 101, 1); + position: relative; + } + + .icon-image { + width: 40px; + height: 40px; + color: white; + } + + .plus-indicator { + position: absolute; + bottom: 2px; + right: -5px; + width: 25px; + height: 25px; + background: #6FB2F6; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 20px; + } + + /* Upload Text */ + .upload-text { + text-align: center; + } + + .upload-text h3 { + margin: 0; + font-size: 1.25rem; + color: #000000; + font-weight: 400; + } + + .upload-text p { + margin-top: 0; + color: #6B7280; + font-size: 0.875rem; + } \ No newline at end of file diff --git a/frontend/src/components/UploadPhoto/UploadArea.jsx b/frontend/src/components/UploadPhoto/UploadArea.jsx new file mode 100644 index 00000000..1a70e7cd --- /dev/null +++ b/frontend/src/components/UploadPhoto/UploadArea.jsx @@ -0,0 +1,75 @@ +import React, { useState } from "react"; +import { PhotoIcon } from "@heroicons/react/24/solid"; +import "./UploadArea.css"; + +export default function UploadArea({ ALLOWED_FILE_TYPES, handleFiles }) { + const [dragActive, setDragActive] = useState(false); + + /** Function to filter valid files */ + const filterValidFiles = (fileList) => + Array.from(fileList).filter((file) => + ALLOWED_FILE_TYPES.includes(file.type) + ); + + /** Handle drag events */ + const handleDrag = (e) => { + e.preventDefault(); + e.stopPropagation(); + + switch (e.type) { + case "dragenter": + case "dragover": + setDragActive(true); + break; + case "dragleave": + setDragActive(false); + break; + default: + break; + } + }; + + /** Handle drop event */ + const handleDrop = (e) => { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + + const validFiles = filterValidFiles(e.dataTransfer.files); + handleFiles(validFiles); + }; + + /** Handle file input change */ + const handleFileInput = (e) => { + const validFiles = filterValidFiles(e.target.files); + handleFiles(validFiles); + }; + + return ( +
+ + +
+ ); +} diff --git a/frontend/src/pages/home/Home.jsx b/frontend/src/pages/home/Home.jsx index be16e658..e4c65fa0 100644 --- a/frontend/src/pages/home/Home.jsx +++ b/frontend/src/pages/home/Home.jsx @@ -10,6 +10,7 @@ import { ChevronRightIcon, ClockIcon } from "@heroicons/react/24/outline"; import "./Home.css"; import { useAuth } from "../../contexts/AuthContext"; import { NavContentWrapper } from "../../components/NavContentWrapper/NavContentWrapper"; +import { NewLogModal } from "../../components/NewLogModal/NewLogModal"; export default function Home() { return ( @@ -97,9 +98,7 @@ function MainContent() { with just a click of a button!

- - Create New Log - + Add Logbook diff --git a/frontend/src/pages/log_history/LogHistory.css b/frontend/src/pages/log_history/LogHistory.css index 56afd8d9..805ead66 100644 --- a/frontend/src/pages/log_history/LogHistory.css +++ b/frontend/src/pages/log_history/LogHistory.css @@ -2,7 +2,7 @@ .table-container { background: transparent; border-radius: 8px; - margin-top: 9%; + margin-top: 8%; margin-left: 6%; width: 90%; } diff --git a/frontend/src/pages/upload_photo/UploadPhoto.css b/frontend/src/pages/upload_photo/UploadPhoto.css index 2a705643..1cb6e6af 100644 --- a/frontend/src/pages/upload_photo/UploadPhoto.css +++ b/frontend/src/pages/upload_photo/UploadPhoto.css @@ -7,94 +7,6 @@ width: 90%; } - /* Upload Area */ - .upload-area { - width: 45%; - margin-left: 23%; - aspect-ratio: 3/2; - border: 2px dashed rgba(69, 72, 76, 0.3); - border-radius: 30px; - display: flex; - align-items: center; - justify-content: center; - position: relative; - } - - .upload-area.drag-active { - border-color: #2B4B96; - background-color: rgba(43, 75, 150, 0.05); - } - - .file-input { - opacity: 0; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - cursor: pointer; - } - - /* Upload Label */ - .upload-label { - display: flex; - flex-direction: column; - align-items: center; - gap: 1rem; - pointer-events: none; - } - - .upload-icon { - width: 80px; - height: 80px; - background: #333333; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 0 20px rgba(30, 55, 101, 1); - position: relative; - } - - .icon-image { - width: 40px; - height: 40px; - color: white; - } - - .plus-indicator { - position: absolute; - bottom: 2px; - right: -5px; - width: 25px; - height: 25px; - background: #6FB2F6; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - color: white; - font-size: 20px; - } - - /* Upload Text */ - .upload-text { - text-align: center; - } - - .upload-text h3 { - margin: 0; - font-size: 1.25rem; - color: #000000; - font-weight: 400; - } - - .upload-text p { - margin-top: 0; - color: #6B7280; - font-size: 0.875rem; - } - /* Upload Controls */ .upload-controls { display: flex; @@ -141,179 +53,6 @@ color: #64B2F6; } - /* Preview Section */ - .preview-section { - position: fixed; - top: 15%; - right: 2%; - width: 400px; - height: 75%; - border-radius: 10px; - background: #FAF9F9; - box-shadow: 8px 0 30px rgba(0, 0, 0, 0.15); - padding: 2rem; - z-index: 1000; - display: flex; - flex-direction: column; - } - - .preview-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; - padding-bottom: 0.5rem; - border-bottom: 2px solid #ECEBED; - } - - .preview-title { - display: flex; - align-items: center; - gap: 0.5rem; - color: #333333; - font-weight: 600; - background: none; - } - - .preview-section-icon { - color: #2B4B96; - width: 20px; - height: 20px; - } - - /* Edit Button */ - .edit-button { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem; - border: 1px solid #9AB0E1; - border-radius: 15px; - background: none; - color: #333333; - cursor: pointer; - transition: color 0.2s ease; - } - - .edit-button.active { - color: #2B4B96; - } - - .edit-icon { - width: 16px; - height: 16px; - } - - /* Delete Button */ - .delete-button { - position: absolute; - top: 1rem; - right: 1.5rem; - width: 24px; - height: 24px; - border-radius: 50%; - background: rgba(0, 0, 0, 0.5); - border: none; - color: white; - font-size: 18px; - line-height: 1; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: background-color 0.2s ease; - } - - .delete-button:hover { - background: rgba(0, 0, 0, 0.7); - } - - /* Preview List */ - .preview-list { - flex-grow: 1; - overflow-y: auto; - overflow-x: hidden; - border-radius: 20px; - display: flex; - flex-direction: column; - gap: 0.5rem; - padding: 0.5rem; - margin-bottom: 0.5rem; - } - - .preview-list::-webkit-scrollbar { - width: 6px; - } - - .preview-list::-webkit-scrollbar-track { - background: #F3F4F6; - border-radius: 3px; - } - - .preview-list::-webkit-scrollbar-thumb { - background: #D1D5DB; - border-radius: 3px; - } - - .preview-list::-webkit-scrollbar-thumb:hover { - background: #9CA3AF; - } - - /* Preview Item */ - .preview-item { - width: 100%; - padding: 0.5rem; - position: relative; - } - - .preview-image { - width: calc(100% - 8px); - border-radius: 12px; - object-fit: contain; - max-height: 500px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - } - - /* Preview Footer */ - .preview-footer { - margin-top: auto; - display: flex; - justify-content: flex-end; - } - - .preview-footer .transcribe-button { - margin-bottom: -1rem; - border-radius: 20px; - padding: 10px 14px; - font-size: 14px; - } - - /* Transcribe Button */ - .transcribe-button { - background-color: #244B94; - color: white; - border: none; - border-radius: 15px; - padding: 12px 20px; - font-size: 16px; - font-weight: 600; - cursor: pointer; - transition: background-color 0.2s ease; - } - - .transcribe-button:hover { - background-color: #1e3a7b; - } - - .transcribe-button:active { - background-color: #152c5f; - } - - .transcribe-button:focus { - outline: none; - box-shadow: 0 0 0 2px rgba(43, 75, 150, 0.4); - } - /* Fixed Transcribe Button */ .fixed-transcribe-button, .transcribe-button-container { diff --git a/frontend/src/pages/upload_photo/UploadPhoto.jsx b/frontend/src/pages/upload_photo/UploadPhoto.jsx index bf6759f9..24aae33e 100644 --- a/frontend/src/pages/upload_photo/UploadPhoto.jsx +++ b/frontend/src/pages/upload_photo/UploadPhoto.jsx @@ -1,15 +1,11 @@ import { useState, useEffect } from "react"; import { NavContentWrapper } from "../../components/NavContentWrapper/NavContentWrapper"; -import { - PhotoIcon, - ChevronLeftIcon, - ChevronRightIcon, - ChevronDoubleRightIcon, - PencilSquareIcon, -} from "@heroicons/react/24/solid"; import ContentHeader from "../../components/ContentHeader/ContentHeader"; import "./UploadPhoto.css"; import { useNavigate } from "react-router-dom"; +import PreviewSection from "../../components/UploadPhoto/PreviewSection"; +import UploadArea from "../../components/UploadPhoto/UploadArea"; +import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/solid"; export default function UploadPhoto() { const navigate = useNavigate(); @@ -33,38 +29,12 @@ export default function UploadPhoto() { } function MainContent({ handleTranscribe }) { - const [dragActive, setDragActive] = useState(false); const [files, setFiles] = useState([]); const [showPreview, setShowPreview] = useState(false); - const [isEditing, setIsEditing] = useState(false); /** Allowed file types */ const ALLOWED_FILE_TYPES = ["image/png", "image/jpeg"]; - /** Function to filter valid files */ - const filterValidFiles = (fileList) => - Array.from(fileList).filter((file) => - ALLOWED_FILE_TYPES.includes(file.type) - ); - - /** Handle drag events */ - const handleDrag = (e) => { - e.preventDefault(); - e.stopPropagation(); - - switch (e.type) { - case "dragenter": - case "dragover": - setDragActive(true); - break; - case "dragleave": - setDragActive(false); - break; - default: - break; - } - }; - /** Handle files from input or drop */ const handleFiles = (newFiles) => { const filesWithPreview = newFiles.map((file) => ({ @@ -75,47 +45,11 @@ function MainContent({ handleTranscribe }) { setFiles((prev) => [...filesWithPreview, ...prev]); // Add new files to beginning }; - /** Handle drop event */ - const handleDrop = (e) => { - e.preventDefault(); - e.stopPropagation(); - setDragActive(false); - - const validFiles = filterValidFiles(e.dataTransfer.files); - handleFiles(validFiles); - }; - - /** Handle file input change */ - const handleFileInput = (e) => { - const validFiles = filterValidFiles(e.target.files); - handleFiles(validFiles); - }; - /** Toggle preview visibility */ const handlePreviewClick = () => { setShowPreview((prev) => !prev); }; - /** Toggle edit mode */ - const handleEdit = () => { - setIsEditing((prev) => !prev); - }; - - /** Handle image deletion */ - const handleDeleteImage = (timestampToDelete) => { - setFiles((prevFiles) => { - // Find the file to delete and revoke its URL - const fileToDelete = prevFiles.find( - (file) => file.timestamp === timestampToDelete - ); - if (fileToDelete?.preview) { - URL.revokeObjectURL(fileToDelete.preview); - } - // Filter out the deleted file - return prevFiles.filter((file) => file.timestamp !== timestampToDelete); - }); - }; - /** Clean up object URLs when component unmounts or files change */ useEffect(() => { return () => { @@ -132,74 +66,18 @@ function MainContent({ handleTranscribe }) { {showPreview && ( -
-
- - -
- -
- {files.map((fileData, index) => ( -
- {`Preview - {isEditing && ( - - )} -
- ))} -
- -
- -
-
+ )} -
- - -
+