diff --git a/.husky/pre-commit b/.husky/pre-commit index 0c33e0d7..d045d816 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -5,14 +5,20 @@ sh ./lizard.sh # Only run frontend lint if the directory exists if [ -d "frontend" ]; then - cd frontend && npm run lint + cd frontend + npx lint-staged + npm run lint + cd .. fi cd $project_root -# Add backend linting +# Add backend linting and formatting if [ -d "backend" ]; then - cd backend && npm run lint + cd backend + npx lint-staged + npm run lint + cd .. fi exit 0 diff --git a/backend/package.json b/backend/package.json index fc11e452..39e7d9a7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,6 +26,11 @@ "cdk:destroy": "dotenv -- npx cdk destroy", "cdk:synth": "dotenv -- npx cdk synth" }, + "lint-staged": { + "*.ts": [ + "prettier --write" + ] + }, "dependencies": { "@aws-sdk/client-bedrock": "^3.782.0", "@aws-sdk/client-bedrock-runtime": "^3.782.0", diff --git a/frontend/package.json b/frontend/package.json index 982f9092..5ee6544f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,8 +28,12 @@ "lint-staged": { "*.{ts,tsx}": [ "eslint --fix", + "prettier --write", "vitest related --run" ], + "*.{js,jsx,css,scss,md,json}": [ + "prettier --write" + ], "*.scss": [ "stylelint --fix" ] diff --git a/frontend/src/common/components/Icon/SvgIcon.scss b/frontend/src/common/components/Icon/SvgIcon.scss index 87f6be6f..bd2f2470 100644 --- a/frontend/src/common/components/Icon/SvgIcon.scss +++ b/frontend/src/common/components/Icon/SvgIcon.scss @@ -8,13 +8,15 @@ max-width: 100%; height: auto; /* CSS filter to display SVGs in #ABBCCD color */ - filter: brightness(0) saturate(100%) invert(84%) sepia(5%) saturate(1013%) hue-rotate(178deg) brightness(89%) contrast(84%); + filter: brightness(0) saturate(100%) invert(84%) sepia(5%) saturate(1013%) hue-rotate(178deg) + brightness(89%) contrast(84%); } &--active { img { /* The active pink color from the design - using precise filter values for #FD7BF4 */ - filter: brightness(0) saturate(100%) invert(65%) sepia(82%) saturate(5793%) hue-rotate(292deg) brightness(99%) contrast(98%); + filter: brightness(0) saturate(100%) invert(65%) sepia(82%) saturate(5793%) hue-rotate(292deg) + brightness(99%) contrast(98%); } } } diff --git a/frontend/src/common/components/Icon/SvgIcon.tsx b/frontend/src/common/components/Icon/SvgIcon.tsx index ea0d9663..8a241862 100644 --- a/frontend/src/common/components/Icon/SvgIcon.tsx +++ b/frontend/src/common/components/Icon/SvgIcon.tsx @@ -73,11 +73,7 @@ const SvgIcon = ({ return ( diff --git a/frontend/src/common/components/Modal/ConfirmationModal.scss b/frontend/src/common/components/Modal/ConfirmationModal.scss index a3b0d0f3..3d2122cc 100644 --- a/frontend/src/common/components/Modal/ConfirmationModal.scss +++ b/frontend/src/common/components/Modal/ConfirmationModal.scss @@ -75,7 +75,7 @@ // Handle the pull indicator &::part(handle) { - background: #D9D9D9; + background: #d9d9d9; width: 36px; height: 4px; border-radius: 2px; diff --git a/frontend/src/common/components/Modal/ConfirmationModal.tsx b/frontend/src/common/components/Modal/ConfirmationModal.tsx index 88956d7f..de387c7d 100644 --- a/frontend/src/common/components/Modal/ConfirmationModal.tsx +++ b/frontend/src/common/components/Modal/ConfirmationModal.tsx @@ -30,10 +30,8 @@ const ConfirmationModal: React.FC = ({ testid = 'confirmation-modal', }) => { // Replace placeholder with actual item name if provided - const formattedMessage = itemName - ? message.replace('{itemName}', `"${itemName}"`) - : message; - + const formattedMessage = itemName ? message.replace('{itemName}', `"${itemName}"`) : message; + return ( = ({ >

{title}

- -

- {formattedMessage} -

- + +

{formattedMessage}

+
= ({ > {cancelText} - + = ({ ); }; -export default ConfirmationModal; \ No newline at end of file +export default ConfirmationModal; diff --git a/frontend/src/common/components/Router/TabNavigation.scss b/frontend/src/common/components/Router/TabNavigation.scss index ccefffae..36eee2b0 100644 --- a/frontend/src/common/components/Router/TabNavigation.scss +++ b/frontend/src/common/components/Router/TabNavigation.scss @@ -10,7 +10,7 @@ padding-bottom: 0; &-button { - --color: #ABBCCD; + --color: #abbccd; --color-selected: #fd7bf4; /* Ensure consistent alignment across platforms */ @@ -21,7 +21,7 @@ &-icon { margin: 0; font-size: 1.25rem; - color: #ABBCCD; + color: #abbccd; } &.tab-selected { @@ -31,7 +31,8 @@ /* Apply to SvgIcon component */ &.ls-svg-icon img { /* Pink color #FD7BF4 using precise filter values */ - filter: brightness(0) saturate(100%) invert(65%) sepia(82%) saturate(5793%) hue-rotate(292deg) brightness(99%) contrast(98%); + filter: brightness(0) saturate(100%) invert(65%) sepia(82%) saturate(5793%) + hue-rotate(292deg) brightness(99%) contrast(98%); } } } diff --git a/frontend/src/common/components/Router/__tests__/TabNavigation.test.tsx b/frontend/src/common/components/Router/__tests__/TabNavigation.test.tsx index c1db3311..90667db9 100644 --- a/frontend/src/common/components/Router/__tests__/TabNavigation.test.tsx +++ b/frontend/src/common/components/Router/__tests__/TabNavigation.test.tsx @@ -231,19 +231,19 @@ vi.mock('../../Icon/SvgIcon', () => ({ // Mock the SVG icons vi.mock('assets/icons/home.svg', () => ({ - default: 'mocked-home-icon.svg' + default: 'mocked-home-icon.svg', })); vi.mock('assets/icons/reports.svg', () => ({ - default: 'mocked-reports-icon.svg' + default: 'mocked-reports-icon.svg', })); vi.mock('assets/icons/upload.svg', () => ({ - default: 'mocked-upload-icon.svg' + default: 'mocked-upload-icon.svg', })); vi.mock('assets/icons/chat.svg', () => ({ - default: 'mocked-chat-icon.svg' + default: 'mocked-chat-icon.svg', })); vi.mock('assets/icons/profile.svg', () => ({ - default: 'mocked-profile-icon.svg' + default: 'mocked-profile-icon.svg', })); // Mock the UploadModal component diff --git a/frontend/src/common/components/Upload/UploadModal.tsx b/frontend/src/common/components/Upload/UploadModal.tsx index 78d461ef..3188b58e 100644 --- a/frontend/src/common/components/Upload/UploadModal.tsx +++ b/frontend/src/common/components/Upload/UploadModal.tsx @@ -190,7 +190,8 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
{file.name}
- {formatFileSize(file.size)} • {Math.ceil((1 - progress) * 10)} {t('upload.secondsLeft', { ns: 'common' })} + {formatFileSize(file.size)} • {Math.ceil((1 - progress) * 10)}{' '} + {t('upload.secondsLeft', { ns: 'common' })}
{/* Progress bar */} diff --git a/frontend/src/pages/Home/components/ReportItem/ReportItem.scss b/frontend/src/pages/Home/components/ReportItem/ReportItem.scss index 17f9621a..15a47f10 100644 --- a/frontend/src/pages/Home/components/ReportItem/ReportItem.scss +++ b/frontend/src/pages/Home/components/ReportItem/ReportItem.scss @@ -13,13 +13,13 @@ } &__status-bullet { - width: 8px; - height: 8px; - margin-top: 5px; - display: inline-block; - background-color: #fead7f; - border-radius: 50%; - vertical-align: middle; + width: 8px; + height: 8px; + margin-top: 5px; + display: inline-block; + background-color: #fead7f; + border-radius: 50%; + vertical-align: middle; } &__category-label { @@ -51,15 +51,14 @@ display: flex; align-items: center; justify-content: center; - background-color: #FFFFFF; + background-color: #ffffff; border-radius: 50%; transition: background-color 0.2s ease; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); &--active { - background-color: #435FF0; + background-color: #435ff0; height: 34px; - } &-icon { diff --git a/frontend/src/pages/Home/components/ReportItem/ReportItem.tsx b/frontend/src/pages/Home/components/ReportItem/ReportItem.tsx index 6c192767..def51035 100644 --- a/frontend/src/pages/Home/components/ReportItem/ReportItem.tsx +++ b/frontend/src/pages/Home/components/ReportItem/ReportItem.tsx @@ -85,10 +85,10 @@ const ReportItem: React.FC = ({
{getCategoryIcon()} {status === 'UNREAD' && ( -
-
-
- )} +
+
+
+ )}
{t(getCategoryTranslationKey(), { ns: 'report' })} diff --git a/frontend/src/pages/Processing/ProcessingPage.scss b/frontend/src/pages/Processing/ProcessingPage.scss index 68c97ec6..796c995a 100644 --- a/frontend/src/pages/Processing/ProcessingPage.scss +++ b/frontend/src/pages/Processing/ProcessingPage.scss @@ -127,16 +127,16 @@ } &__error-subheading { - font-size: 22px; + font-size: 20px; font-weight: 600; color: #1c1c1e; margin: 0 0 16px; } &__error-message { - font-size: 17px; - line-height: 24px; - color: #8e8e93; + font-size: 15px; + line-height: 22px; + color: #505055; // Changed from #8e8e93 to a darker shade margin: 0 auto 40px; max-width: 290px; } @@ -146,11 +146,11 @@ flex-direction: row; gap: 12px; width: 100%; - padding: 0 16px; - max-width: 400px; + padding: 0 8px; // Reduced side padding to make buttons wider + max-width: 450px; // Increased max-width from 400px margin-left: auto; margin-right: auto; - margin-bottom: 32px; + margin-bottom: 8px; // Further reduced from 20px to 8px ion-button { margin: 0; diff --git a/frontend/src/pages/Processing/ProcessingPage.tsx b/frontend/src/pages/Processing/ProcessingPage.tsx index 2c0846db..27bf5c2f 100644 --- a/frontend/src/pages/Processing/ProcessingPage.tsx +++ b/frontend/src/pages/Processing/ProcessingPage.tsx @@ -26,7 +26,6 @@ const ProcessingPage: React.FC = () => { const [processingError, setProcessingError] = useState(null); const [errorHeading, setErrorHeading] = useState(null); const statusCheckIntervalRef = useRef(null); - const lastTriggeredTime = useRef(null); // Get the location state which may contain the reportId (previously filePath) const location = useLocation<{ reportId: string }>(); @@ -127,17 +126,12 @@ const ProcessingPage: React.FC = () => { // Reset error state and try processing the same file again setError(null, null); setIsProcessing(true); - lastTriggeredTime.current = Date.now(); processFile(); }; // Send the API request when component mounts useEffect(() => { - // check last triggered time to prevent multiple calls, if it's within 100ms then ignore - if ((lastTriggeredTime.current && lastTriggeredTime.current > Date.now() - 100) || !reportId) { - return; - } - + clearStatusCheckInterval(); execute(); // Clean up the interval when the component unmounts diff --git a/frontend/src/pages/Reports/ReportDetailPage.scss b/frontend/src/pages/Reports/ReportDetailPage.scss index 9789e7ef..4ff923de 100644 --- a/frontend/src/pages/Reports/ReportDetailPage.scss +++ b/frontend/src/pages/Reports/ReportDetailPage.scss @@ -2,8 +2,8 @@ --padding-horizontal: 16px; --page-background: radial-gradient( circle, - rgba(250, 250, 255, 0.83) 0%, - rgba(249, 252, 255, 1) 100% + rgb(250 250 255 / 83%) 0%, + rgb(249 252 255 / 100%) 100% ); ion-content { @@ -50,7 +50,6 @@ justify-content: space-between; align-items: center; margin-top: 16px; - margin-bottom: 4px; } &__category { @@ -72,19 +71,19 @@ justify-content: center; color: #838b94; cursor: pointer; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + box-shadow: 0 2px 4px rgb(0 0 0 / 5%); } &__bookmark-button--active { background-color: #435ff0; - color: #ffffff; + color: #fff; } &__subtitle { font-family: var(--font-family-base); font-size: 18px; font-weight: 600; - margin: 4px 0 16px; + margin: 0 0 16px; color: #313e4c; } @@ -102,7 +101,7 @@ display: flex; align-items: center; justify-content: center; - font-family: 'Inter', sans-serif; + font-family: Inter, sans-serif; font-size: 13px; font-weight: 600; color: #838b94; @@ -114,7 +113,7 @@ &--active { background-color: white; color: #313e4c; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + box-shadow: 0 2px 4px rgb(0 0 0 / 5%); } } @@ -129,7 +128,6 @@ padding: 14px 16px; margin: 0 16px 16px; align-items: flex-start; - background-color: rgba(201, 58, 84, 0.08); gap: 12px; } @@ -162,7 +160,9 @@ align-items: center; padding: 16px; cursor: pointer; - background-color: rgba(235, 238, 248, 0.5); + background-color: rgb(235 238 248 / 50%); + margin-left: -15px; + margin-bottom: 14px; } &__section-icon { @@ -238,12 +238,12 @@ } &__item { - background-color: #ffffff; + background-color: #fff; border-radius: 16px; padding: 0; margin: 16px; overflow: hidden; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 4px rgb(0 0 0 / 5%); border: 1px solid #ebeef8; } @@ -251,17 +251,15 @@ display: flex; flex-wrap: wrap; align-items: center; - padding: 16px; - background-color: #fff; + padding: 16px 16px 16px 24px; // Increased left padding from 16px to 24px + background-color: #fff; // Keep default background white &--high { - background-color: rgba(201, 58, 84, 0.08); - border-bottom: 1px solid rgba(201, 58, 84, 0.15); + border-bottom: 1px solid rgb(201 58 84 / 15%); } &--low { - background-color: rgba(250, 173, 113, 0.08); - border-bottom: 1px solid rgba(250, 173, 113, 0.15); + border-bottom: 1px solid rgb(250 173 113 / 15%); } } @@ -304,7 +302,13 @@ &__item-details { color: #444; padding: 16px; - background-color: rgba(248, 249, 251, 0.6); + background-color: rgb(248 249 251 / 60%); // Default background + + &.details--high, // Target details when high + &.details--low { + // Target details when low + background-color: transparent; // Remove specific background for flagged item details + } } &__item-section { @@ -317,7 +321,7 @@ h4 { font-size: 15px; color: #313e4c; - margin: 0 0 8px 0; + margin: 0 0 8px; font-weight: 600; } @@ -367,7 +371,7 @@ align-items: center; justify-content: center; letter-spacing: 0.36px; - font-family: 'Inter', sans-serif; + font-family: Inter, sans-serif; &--discard { background-color: #fff; @@ -375,7 +379,7 @@ border: 1px solid #af1b3f; &:hover { - background-color: rgba(201, 58, 84, 0.05); + background-color: rgb(201 58 84 / 5%); } } @@ -394,7 +398,7 @@ padding: 0 16px 16px; border-radius: 16px; background-color: #faf8fd; - border: 1px solid rgba(137, 117, 211, 0.2); + border: 1px solid rgb(137 117 211 / 20%); text-align: center; display: flex; align-items: center; @@ -442,7 +446,7 @@ // Original Report Tab Styles &__original-report { - padding: 0 16px; + padding: 0; margin: 16px 16px 20px; } @@ -465,7 +469,7 @@ } &__label-text { - font-family: 'Inter', sans-serif; + font-family: Inter, sans-serif; font-weight: 600; font-size: 18px; color: #313e4c; @@ -473,54 +477,162 @@ } &__results-table { - background-color: #fff; border-radius: 16px; - border: 1px solid #ebeef8; - margin-bottom: 16px; + box-shadow: 0 2px 8px 0 rgb(44 62 80 / 6%); + border: 1px solid #e9eef6; + background: #fff; overflow: hidden; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02); + margin-bottom: 20px; + width: 100%; + min-width: 0; + box-sizing: border-box; } &__results-header { + background: #fff; + } + + &__results-header-row { display: flex; - background-color: #f2f4f7; - padding: 8px 16px; + background: #f2f4f7; + color: #5c6d80; + font-weight: 600; + padding: 5px; + width: 90%; + border-bottom: 1.5px solid #e9eef6; + align-items: center; + margin-top: 15px; + margin-right: auto; + margin-left: auto; + } + + &__results-row { + display: flex; + align-items: center; + min-height: 48px; + padding: 0 14px; + border-bottom: 1.5px solid #e9eef6; + background: #fff; + min-width: 0; + + &:last-child { + border-bottom: none; + } + + &--flagged { + background-color: rgb(201 58 84 / 2%); + } } &__results-cell { - font-family: var(--font-family-base); + display: flex; + align-items: center; + font-size: 14px; + color: #313e4c; + font-weight: 400; + padding: 0; + background: none; + min-width: 0; + white-space: nowrap; + max-width: 100%; + line-height: 1.2; + } + + &__results-cell-header { + display: flex; + align-items: center; + font-size: 14px; + font-weight: 400; + padding: 0; + background: none; + min-width: 0; + white-space: nowrap; + max-width: 100%; + line-height: 1.2; + } + + &__results-cell--test { + flex: 2; font-size: 12px; - font-weight: 600; + min-width: 160px; + overflow-wrap: break-word; + white-space: normal; // Allow text to wrap + word-break: break-word; // Ensure long words break + } + + &__results-cell--value { + flex: 1.3; + font-size: 12px; + justify-content: flex-end; + font-weight: 500; + white-space: normal; // Allow text to wrap + word-break: break-word; // Ensure long words break + overflow-wrap: break-word; + } + + &__results-cell--ref { color: #5c6d80; + flex: 1; + justify-content: flex-end; + font-size: 12px; + min-width: 80px; + white-space: normal; // Allow text to wrap + word-break: break-word; // Ensure long words break + overflow-wrap: break-word; + } - &--test { - flex: 1; - text-align: left; - font-family: var(--font-family-base); - font-size: 13px; - color: #313e4c; - font-weight: 400; - } + &__results-cell-header--test { + flex: 2; + min-width: 160px; + overflow-wrap: break-word; + white-space: normal; // Allow text to wrap + word-break: break-word; // Ensure long words break + } - &--value { - width: 100px; - text-align: right; - font-family: var(--font-family-base); - font-size: 13px; - color: #313e4c; - font-weight: 600; - padding-right: 10px; - } + &__results-cell-header--value { + flex: 1.3; + justify-content: flex-end; + white-space: normal; // Allow text to wrap + word-break: break-word; // Ensure long words break + overflow-wrap: break-word; + } - &--ref { - width: 80px; - text-align: right; - font-family: var(--font-family-base); - font-size: 12px; - color: #5c6d80; - font-weight: 400; - padding-left: 10px; - } + &__results-cell-header--ref { + flex: 1; + justify-content: flex-end; + font-size: 13px; + min-width: 80px; + white-space: normal; // Allow text to wrap + word-break: break-word; // Ensure long words break + overflow-wrap: break-word; + } + + &__results-value-wrapper { + display: flex; + gap: 4px; + min-width: 0; + align-items: center; + height: 100%; + } + + &__results-value { + font-weight: 600; + color: #313e4c; + font-size: 13px; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100px; + display: inline-block; + } + + &__results-alert-icon { + width: 16px; + height: 16px; + margin-right: 2px; + vertical-align: middle; + align-self: center; + margin-top: 0; + margin-bottom: 0; } &__critical-label { @@ -549,7 +661,7 @@ justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid #ebeef8; - background-color: rgba(242, 244, 247, 0.1); + background-color: rgb(242 244 247 / 10%); } &__primary-sample-label { @@ -566,55 +678,6 @@ color: #313e4c; } - &__results-row { - display: flex; - padding: 12px 16px; - border-bottom: 1px solid #ebeef8; - - &--flagged { - background-color: rgba(201, 58, 84, 0.02); - } - - // Make sure the last row doesn't have a bottom border - &:last-child { - border-bottom: none; - } - - .report-detail-page__results-cell { - &--test { - flex: 1; - text-align: left; - font-family: var(--font-family-base); - font-size: 13px; - color: #313e4c; - font-weight: 400; - } - - &--value { - display: flex; - justify-content: end; - align-items: center; - width: 100px; - text-align: right; - font-family: var(--font-family-base); - font-size: 13px; - color: #313e4c; - font-weight: 600; - padding-right: 10px; - } - - &--ref { - width: 80px; - text-align: right; - font-family: var(--font-family-base); - font-size: 12px; - color: #5c6d80; - font-weight: 400; - padding-left: 10px; - } - } - } - &__comments-section { background-color: #f0f2ff; border-radius: 16px; @@ -627,7 +690,7 @@ font-size: 18px; font-weight: 600; color: #313e4c; - margin: 0 0 12px 0; + margin: 0 0 12px; letter-spacing: 0.26px; } @@ -653,7 +716,7 @@ font-size: 13px; font-weight: 600; color: #313e4c; - margin: 0 0 16px 0; + margin: 0 0 16px; letter-spacing: 0.26px; } @@ -748,8 +811,141 @@ } } +// Modify styles for both flagged and normal values sections +.flagged-values-section, +.normal-values-section { + background-color: transparent !important; + border: none !important; + box-shadow: none !important; + + .report-detail-page__section-header { + background-color: transparent !important; + padding: 2px 16px !important; + border-radius: 0 !important; + + .report-detail-page__section-title { + font-size: 13px !important; + line-height: 1.1 !important; + } + } + + .flagged-values-content, + .normal-values-content { + padding: 0; + background-color: transparent; + } +} + +// Updated lab value item styles with smaller text and reduced height +.lab-value-item { + border-radius: 16px; + margin-bottom: 14px; // Slightly reduced from 16px + overflow: hidden; + box-shadow: 0 1px 4px rgb(0 0 0 / 5%); + border: 1px solid #ebeef8; + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + background-color: #fefefe; + padding: 8px 16px 8px 28px; // Shrink header vertical padding to 2px for even smaller heading height + } + + &--low &__header { + background-color: #fef8f2; // Light orange for "Low" background + } + + &--high &__header { + background-color: #fef2f2; // Light red for "High" background + } + + &__name { + font-weight: 500; + font-size: 13px; // Further reduce font size for heading + letter-spacing: 0.26px; + line-height: 16px; // Reduce line-height for compactness + color: #313e4c; + flex: 1; + } + + &__status-value { + display: flex; + align-items: center; + gap: 12px; + } + + &__status { + font-size: 11px; // Reduced from 12px for smaller badge + padding: 2px 7px; // Reduced from 3px 10px for smaller badge + border-radius: 12px; + font-weight: 600; + text-transform: capitalize; + + &.report-detail-page__item-level--high { + background-color: #c93a54; + color: white; + } + + &.report-detail-page__item-level--low { + background-color: #f5b77b; + color: white; + } + } + + &__value { + font-weight: 600; + font-size: 15px; // Reduced from 16px + white-space: nowrap; + color: #313e4c; + } + + &__details { + padding: 14px 16px 14px 28px; // Reduced from 16px to 14px vertical padding + } + + &__section { + margin-bottom: 16px; // Reduced from 20px + + &:last-child { + margin-bottom: 0; + } + + h4 { + font-size: 14px; // Reduced from 15px + color: #313e4c; + margin: 0 0 6px; // Reduced from 8px + font-weight: 600; + } + + p { + margin: 0; + font-size: 13px; // Reduced from 14px + line-height: 1.4; // Reduced from 1.5 + color: #5c6d80; + } + } + + &__list { + margin: 0; + padding-left: 20px; + font-size: 13px; // Reduced from 14px + line-height: 1.4; // Reduced from 1.5 + color: #5c6d80; + + li { + margin-bottom: 8px; // Reduced from 10px + padding-left: 4px; + + &:last-child { + margin-bottom: 0; + } + } + } +} + // Media Queries for responsiveness -@media (min-width: 768px) { +@media (width >= 768px) { .report-detail-page { &__actions { max-width: 600px; @@ -782,5 +978,20 @@ margin-left: auto; margin-right: auto; } + + .flagged-values-section, + .lab-value-item { + max-width: 600px; + margin-left: auto; + margin-right: auto; + } + } +} + +@media (width >= 480px) { + .report-detail-page__results-table { + max-width: 420px; + margin-left: auto; + margin-right: auto; } } diff --git a/frontend/src/pages/Reports/ReportsListPage.scss b/frontend/src/pages/Reports/ReportsListPage.scss index 9a4c75fc..698e4248 100644 --- a/frontend/src/pages/Reports/ReportsListPage.scss +++ b/frontend/src/pages/Reports/ReportsListPage.scss @@ -21,7 +21,7 @@ display: flex; align-items: center; justify-content: center; - background-color: #EBECFD; + background-color: #ebecfd; border-radius: 50%; width: 36px; height: 36px; @@ -67,16 +67,16 @@ margin-top: 4px; ion-segment { - --background: #EBEEF8; + --background: #ebeef8; border-radius: 9999px; overflow: hidden; padding: 4px; ion-segment-button { - --background: #EBEEF8; - --background-checked: #FFFFFF; - --color: #313E4C; - --color-checked: #313E4C; + --background: #ebeef8; + --background-checked: #ffffff; + --color: #313e4c; + --color-checked: #313e4c; --indicator-color: transparent; --border-radius: 9999px; --border-style: none; @@ -164,4 +164,3 @@ margin-right: 1rem; } } - diff --git a/frontend/src/pages/Reports/ReportsListPage.tsx b/frontend/src/pages/Reports/ReportsListPage.tsx index 995482ca..4d41ff79 100644 --- a/frontend/src/pages/Reports/ReportsListPage.tsx +++ b/frontend/src/pages/Reports/ReportsListPage.tsx @@ -81,12 +81,12 @@ const ReportsListPage: React.FC = () => { // Filter and sort reports based on selected filter, categories, and sort direction const filteredReports = useMemo(() => { // First, filter the reports by bookmark status - let filtered = filter === 'all' ? reports : reports.filter(report => report.bookmarked); + let filtered = filter === 'all' ? reports : reports.filter((report) => report.bookmarked); // Then, filter by selected categories if any are selected if (selectedCategories.length > 0) { - filtered = filtered.filter(report => - selectedCategories.includes(report.category.toLowerCase()) + filtered = filtered.filter((report) => + selectedCategories.includes(report.category.toLowerCase()), ); } @@ -165,7 +165,7 @@ const ReportsListPage: React.FC = () => { }; const handleRemoveCategory = (categoryId: string) => { - setSelectedCategories(prev => prev.filter(id => id !== categoryId)); + setSelectedCategories((prev) => prev.filter((id) => id !== categoryId)); }; const handleClearAllCategories = () => { @@ -173,7 +173,7 @@ const ReportsListPage: React.FC = () => { }; const getCategoryLabel = (categoryId: string): string => { - const category = categories.find(cat => cat.id === categoryId); + const category = categories.find((cat) => cat.id === categoryId); return category ? category.label : categoryId; }; @@ -182,7 +182,7 @@ const ReportsListPage: React.FC = () => { return (
- {selectedCategories.map(categoryId => ( + {selectedCategories.map((categoryId) => ( = ({ - onDiscard, - onNewUpload, - reportTitle, -}) => { +const ActionButtons: React.FC = ({ onDiscard, onNewUpload, reportTitle }) => { const { t } = useTranslation(); const [showConfirmDiscard, setShowConfirmDiscard] = useState(false); const [showConfirmNewUpload, setShowConfirmNewUpload] = useState(false); diff --git a/frontend/src/pages/Reports/components/AiAnalysisTab.tsx b/frontend/src/pages/Reports/components/AiAnalysisTab.tsx index 302ce02c..928a76ff 100644 --- a/frontend/src/pages/Reports/components/AiAnalysisTab.tsx +++ b/frontend/src/pages/Reports/components/AiAnalysisTab.tsx @@ -60,7 +60,7 @@ const AiAnalysisTab: React.FC = ({ isExpanded={normalValuesExpanded} onToggle={toggleNormalValues} /> - + {/* AI Assistant Notice */}
diff --git a/frontend/src/pages/Reports/components/AiAssistantNotice.scss b/frontend/src/pages/Reports/components/AiAssistantNotice.scss index 8f1af6ca..dc29f4f4 100644 --- a/frontend/src/pages/Reports/components/AiAssistantNotice.scss +++ b/frontend/src/pages/Reports/components/AiAssistantNotice.scss @@ -2,27 +2,27 @@ padding: 20px 0; display: flex; justify-content: center; - + &__content { display: flex; flex-direction: column; align-items: center; text-align: center; } - + .clarification-text { font-size: 15px; margin-bottom: 8px; } - + .assistant-link { - color: #6177FF; + color: #6177ff; font-size: 15px; cursor: pointer; font-weight: 500; - + &:hover { text-decoration: underline; } } -} \ No newline at end of file +} diff --git a/frontend/src/pages/Reports/components/AiAssistantNotice.tsx b/frontend/src/pages/Reports/components/AiAssistantNotice.tsx index a6bb3643..f257a1b3 100644 --- a/frontend/src/pages/Reports/components/AiAssistantNotice.tsx +++ b/frontend/src/pages/Reports/components/AiAssistantNotice.tsx @@ -19,16 +19,13 @@ const AiAssistantNotice: React.FC = () => { Still need further clarifications? - -
+ +
Ask our AI Assistant >
- { ); }; -export default AiAssistantNotice; \ No newline at end of file +export default AiAssistantNotice; diff --git a/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss b/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss index 5c2dff43..84002356 100644 --- a/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss +++ b/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss @@ -34,7 +34,7 @@ &__category-button { background-color: white; - border: 1px solid #838B94; + border: 1px solid #838b94; border-radius: 8px; padding: 0.75rem 0.5rem; font-size: 14px; @@ -51,9 +51,9 @@ &--selected { font-weight: 600; - background-color: #313E4C; + background-color: #313e4c; color: white; - border-color: #313E4C; + border-color: #313e4c; } } @@ -64,7 +64,7 @@ &__apply-button { font-family: var(--font-family-base); - --background: #435FF0; + --background: #435ff0; --color: white; height: 48px; font-weight: 600; diff --git a/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.tsx b/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.tsx index 10c06d59..37296ce0 100644 --- a/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.tsx +++ b/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.tsx @@ -50,7 +50,9 @@ const FilterPanel: React.FC = ({
- + {t('filter.apply', { ns: 'report' })}
diff --git a/frontend/src/pages/Reports/components/FlaggedValuesSection.tsx b/frontend/src/pages/Reports/components/FlaggedValuesSection.tsx index 6c2bcffa..6ab131b9 100644 --- a/frontend/src/pages/Reports/components/FlaggedValuesSection.tsx +++ b/frontend/src/pages/Reports/components/FlaggedValuesSection.tsx @@ -19,7 +19,7 @@ const FlaggedValuesSection: React.FC = ({ const { t } = useTranslation(); return ( -
+
Flagged @@ -32,7 +32,13 @@ const FlaggedValuesSection: React.FC = ({
- {isExpanded && flaggedValues.map((item, index) => )} + {isExpanded && ( +
+ {flaggedValues.map((item, index) => ( + + ))} +
+ )}
); }; diff --git a/frontend/src/pages/Reports/components/LabValueItem.tsx b/frontend/src/pages/Reports/components/LabValueItem.tsx index fb8ef44e..0594a314 100644 --- a/frontend/src/pages/Reports/components/LabValueItem.tsx +++ b/frontend/src/pages/Reports/components/LabValueItem.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { LabValue } from '../../../common/models/medicalReport'; +import classNames from 'classnames'; interface LabValueItemProps { item: LabValue; @@ -71,39 +72,42 @@ const LabValueItem: React.FC = ({ item }) => { }; const statusInfo = getStatusInfo(); + const isFlagged = item.status !== 'normal'; + const statusClass = item.status.toLowerCase(); // 'high' or 'low' + + // Handle potential parenthesis in the name (e.g., "Low Hemoglobin (10.1 g/dL)") + const itemName = item.name; return ( -
-
-
{item.name}
- {item.status !== 'normal' && ( -
- {statusInfo.text} +
+
+
{itemName}
+
+ {item.status !== 'normal' && ( +
+ {statusInfo.text} +
+ )} +
+ {item.value} {item.unit}
- )} -
- {item.value} {item.unit}
-
-
+
+

{t('report.conclusion.title', { ns: 'reportDetail', defaultValue: 'Conclusion' })}:

{item.conclusion}

-
+

{t('report.suggestions.title', { ns: 'reportDetail', defaultValue: 'Suggestions' })}:

{suggestionItems.length > 0 ? ( -
    +
      {suggestionItems.map((suggestion, index) => (
    • {suggestion}
    • ))} diff --git a/frontend/src/pages/Reports/components/NormalValuesSection.tsx b/frontend/src/pages/Reports/components/NormalValuesSection.tsx index bb196eb8..37c5d375 100644 --- a/frontend/src/pages/Reports/components/NormalValuesSection.tsx +++ b/frontend/src/pages/Reports/components/NormalValuesSection.tsx @@ -20,7 +20,7 @@ const NormalValuesSection: React.FC = ({ const { t } = useTranslation(); return ( -
      +
      = ({
      )} - {isExpanded && - normalValues.length > 0 && - normalValues.map((item, index) => )} + {isExpanded && normalValues.length > 0 && ( +
      + {normalValues.map((item, index) => ( + + ))} +
      + )}
      ); }; diff --git a/frontend/src/pages/Reports/components/OriginalReportTab.tsx b/frontend/src/pages/Reports/components/OriginalReportTab.tsx index 59dc84bd..b6e18a28 100644 --- a/frontend/src/pages/Reports/components/OriginalReportTab.tsx +++ b/frontend/src/pages/Reports/components/OriginalReportTab.tsx @@ -29,37 +29,48 @@ const OriginalReportTab: React.FC = ({ reportData }) => {/* Test results table */}
      -
      - Test -
      -
      - Results -
      -
      - Ref. +
      +
      + Test +
      +
      + {/* Status column */} +
      +
      + Results +
      +
      + Ref. +
      - - {/* Test Results Rows */} {reportData.labValues.map((labValue, index) => (
      -

      {labValue.name}

      + {labValue.name}
      -
      +
      {labValue.status !== 'normal' && ( {labValue.status )} - -

      +

      +
      + {labValue.value} {labValue.unit} -

      +
      -

      {labValue.normalRange}

      + {labValue.normalRange}
      ))} diff --git a/frontend/src/pages/Reports/components/ReportHeader.tsx b/frontend/src/pages/Reports/components/ReportHeader.tsx index 849f6fda..385d286e 100644 --- a/frontend/src/pages/Reports/components/ReportHeader.tsx +++ b/frontend/src/pages/Reports/components/ReportHeader.tsx @@ -26,6 +26,7 @@ const ReportHeader: React.FC = ({ reportData, onClose }) => {
      {reportData.category && t(`list.${reportData.category}Category`, { ns: 'reportDetail' })} +

      {reportData.title}

      -

      {reportData.title}

      ); }; diff --git a/frontend/src/pages/Reports/components/ReportsFilterEmpty/ReportsFilterEmpty.scss b/frontend/src/pages/Reports/components/ReportsFilterEmpty/ReportsFilterEmpty.scss index 6f817437..53124245 100644 --- a/frontend/src/pages/Reports/components/ReportsFilterEmpty/ReportsFilterEmpty.scss +++ b/frontend/src/pages/Reports/components/ReportsFilterEmpty/ReportsFilterEmpty.scss @@ -16,13 +16,13 @@ width: 80px; height: 80px; border-radius: 50%; - background-color: #EFF2F9; + background-color: #eff2f9; margin-bottom: 1.5rem; } &__icon { font-size: 36px; - color: #9BA7BF; + color: #9ba7bf; } &__title { diff --git a/frontend/src/pages/Reports/components/ReportsFilterEmpty/ReportsFilterEmpty.tsx b/frontend/src/pages/Reports/components/ReportsFilterEmpty/ReportsFilterEmpty.tsx index 0d37bbdc..ad5dfab0 100644 --- a/frontend/src/pages/Reports/components/ReportsFilterEmpty/ReportsFilterEmpty.tsx +++ b/frontend/src/pages/Reports/components/ReportsFilterEmpty/ReportsFilterEmpty.tsx @@ -28,9 +28,7 @@ const ReportsFilterEmpty: React.FC = ({
      -

      - {t('list.noMatchesTitle', { ns: 'report' })} -

      +

      {t('list.noMatchesTitle', { ns: 'report' })}

      {t('list.noMatchesMessage', { ns: 'report' })} diff --git a/frontend/src/test/wrappers/WithMinimalProviders.tsx b/frontend/src/test/wrappers/WithMinimalProviders.tsx index 2d2d13c8..7cd92d95 100644 --- a/frontend/src/test/wrappers/WithMinimalProviders.tsx +++ b/frontend/src/test/wrappers/WithMinimalProviders.tsx @@ -43,7 +43,7 @@ const mockI18n = { options: {}, isResourcesLoaded: true, dir: () => 'ltr', - getFixedT: () => ((key: string) => key), + getFixedT: () => (key: string) => key, format: vi.fn(), formatMessage: vi.fn(), hasLoadedNamespace: () => true, diff --git a/frontend/src/theme/theme-overrides.css b/frontend/src/theme/theme-overrides.css index 1b70bc66..b7e0b51f 100644 --- a/frontend/src/theme/theme-overrides.css +++ b/frontend/src/theme/theme-overrides.css @@ -62,7 +62,9 @@ ion-content { } /* Background for the entire app */ -body, ion-app, ion-content { +body, +ion-app, +ion-content { background: url('../../assets/Reports_bg.png') no-repeat center center !important; } diff --git a/frontend/src/types/vitest.d.ts b/frontend/src/types/vitest.d.ts index 795d90ee..1a727fb3 100644 --- a/frontend/src/types/vitest.d.ts +++ b/frontend/src/types/vitest.d.ts @@ -3,25 +3,25 @@ import 'vitest'; // Extend the expect interface with testing-library matchers declare global { // Add typings for Vitest global functions - const describe: typeof import('vitest')['describe']; - const it: typeof import('vitest')['it']; - const test: typeof import('vitest')['test']; - const expect: typeof import('vitest')['expect']; - const vi: typeof import('vitest')['vi']; - const beforeEach: typeof import('vitest')['beforeEach']; - const afterEach: typeof import('vitest')['afterEach']; - const beforeAll: typeof import('vitest')['beforeAll']; - const afterAll: typeof import('vitest')['afterAll']; - + const describe: (typeof import('vitest'))['describe']; + const it: (typeof import('vitest'))['it']; + const test: (typeof import('vitest'))['test']; + const expect: (typeof import('vitest'))['expect']; + const vi: (typeof import('vitest'))['vi']; + const beforeEach: (typeof import('vitest'))['beforeEach']; + const afterEach: (typeof import('vitest'))['afterEach']; + const beforeAll: (typeof import('vitest'))['beforeAll']; + const afterAll: (typeof import('vitest'))['afterAll']; + // Extend the globalThis interface to allow assigning Vitest functions interface Window { - expect: typeof import('vitest')['expect']; - afterEach: typeof import('vitest')['afterEach']; - beforeEach: typeof import('vitest')['beforeEach']; - describe: typeof import('vitest')['describe']; - it: typeof import('vitest')['it']; - vi: typeof import('vitest')['vi']; - beforeAll: typeof import('vitest')['beforeAll']; - afterAll: typeof import('vitest')['afterAll']; + expect: (typeof import('vitest'))['expect']; + afterEach: (typeof import('vitest'))['afterEach']; + beforeEach: (typeof import('vitest'))['beforeEach']; + describe: (typeof import('vitest'))['describe']; + it: (typeof import('vitest'))['it']; + vi: (typeof import('vitest'))['vi']; + beforeAll: (typeof import('vitest'))['beforeAll']; + afterAll: (typeof import('vitest'))['afterAll']; } -} \ No newline at end of file +} diff --git a/frontend/src/vitest.d.ts b/frontend/src/vitest.d.ts index 82d95785..035228da 100644 --- a/frontend/src/vitest.d.ts +++ b/frontend/src/vitest.d.ts @@ -6,13 +6,13 @@ import type { TestContext } from 'vitest'; declare global { // Declare global testing functions - const describe: typeof import('vitest')['describe']; - const it: typeof import('vitest')['it']; - const test: typeof import('vitest')['test']; - const expect: typeof import('vitest')['expect']; + const describe: (typeof import('vitest'))['describe']; + const it: (typeof import('vitest'))['it']; + const test: (typeof import('vitest'))['test']; + const expect: (typeof import('vitest'))['expect']; const beforeEach: (fn: (context: TestContext) => void) => void; const afterEach: (fn: (context: TestContext) => void) => void; const beforeAll: (fn: (context: TestContext) => void) => void; const afterAll: (fn: (context: TestContext) => void) => void; - const vi: typeof import('vitest')['vi']; + const vi: (typeof import('vitest'))['vi']; }