chore: Data Mocking for Study List (#1515)
* feat: Data mocking for studies * feat: Data Mocking for Study List * fix empty study position * rename variables for numOfStudies * fix studies query * remove alert :| * minor refactor * split utils and refactoring * refactoring -- format, clean up icons, mdx mock
This commit is contained in:
parent
2138565990
commit
2bf5486112
@ -1,3 +1,5 @@
|
||||
import * as utils from './src/utils/';
|
||||
|
||||
export {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
@ -9,4 +11,6 @@ export {
|
||||
Typography,
|
||||
} from './src/components';
|
||||
|
||||
export { utils };
|
||||
|
||||
export { StudyList } from './src/views';
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.10.0",
|
||||
"docz": "^2.2.0",
|
||||
"theme-ui": "^0.2.38",
|
||||
"gatsby": "2.19.24",
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20">
|
||||
<g fill="#348CFD">
|
||||
<path d="M14.75.917c.693 0 1.263.528 1.327 1.205l.006.128v4.167c0 .276-.224.5-.5.5-.245 0-.45-.177-.492-.41l-.008-.09V2.25c0-.161-.114-.295-.266-.327l-.067-.006H2.25c-.161 0-.295.114-.327.266l-.006.067v12.5c0 .161.114.295.266.327l.067.006h4.167c.276 0 .5.224.5.5 0 .246-.177.45-.41.492l-.09.008H2.25c-.693 0-1.263-.528-1.327-1.205l-.006-.128V2.25c0-.693.528-1.263 1.205-1.327L2.25.917h12.5z" transform="translate(-.5 -1)"/>
|
||||
<path d="M19.583 5.917H7.417c-.829 0-1.5.671-1.5 1.5v12.166c0 .829.671 1.5 1.5 1.5h12.166c.829 0 1.5-.671 1.5-1.5V7.417c0-.829-.671-1.5-1.5-1.5zm-12.166 1h12.166c.276 0 .5.224.5.5v12.166c0 .276-.224.5-.5.5H7.417c-.276 0-.5-.224-.5-.5V7.417c0-.276.224-.5.5-.5z" transform="translate(-.5 -1)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 856 B |
7
platform/ui/src/assets/icons/magnifier.svg
Normal file
7
platform/ui/src/assets/icons/magnifier.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="111" height="111" viewBox="0 0 111 111">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#3A3F99" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" transform="translate(2 2)">
|
||||
<circle cx="53.419" cy="53.419" r="53.419" fill="#06081D"/>
|
||||
<circle cx="49.411" cy="49.411" r="23.862"/>
|
||||
<path d="M66.282 66.282L81.29 81.29"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 861 B After Width: | Height: | Size: 861 B |
|
Before Width: | Height: | Size: 849 B After Width: | Height: | Size: 849 B |
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
// Icons
|
||||
import active from './../../assets/icons/active.svg';
|
||||
import seriesActive from './../../assets/icons/series-active.svg';
|
||||
import seriesInactive from './../../assets/icons/series-inactive.svg';
|
||||
import cancel from './../../assets/icons/cancel.svg';
|
||||
import chevronDown from './../../assets/icons/chevron-down.svg';
|
||||
import chevronRight from './../../assets/icons/chevron-right.svg';
|
||||
@ -8,6 +9,7 @@ import infoLink from './../../assets/icons/info-link.svg';
|
||||
import launchArrow from './../../assets/icons/launch-arrow.svg';
|
||||
import launchInfo from './../../assets/icons/launch-info.svg';
|
||||
import logoOhifSmall from './../../assets/icons/logo-ohif-small.svg';
|
||||
import magnifier from './../../assets/icons/magnifier.svg';
|
||||
import notificationwarningDiamond from './../../assets/icons/notificationwarning-diamond.svg';
|
||||
import settings from './../../assets/icons/settings.svg';
|
||||
import sorting from './../../assets/icons/sorting.svg';
|
||||
@ -15,7 +17,8 @@ import sortingActiveDown from './../../assets/icons/sorting-active-down.svg';
|
||||
import sortingActiveUp from './../../assets/icons/sorting-active-up.svg';
|
||||
|
||||
const ICONS = {
|
||||
active: active,
|
||||
'series-active': seriesActive,
|
||||
'series-inactive': seriesInactive,
|
||||
cancel: cancel,
|
||||
'chevron-down': chevronDown,
|
||||
'chevron-right': chevronRight,
|
||||
@ -23,6 +26,7 @@ const ICONS = {
|
||||
'launch-arrow': launchArrow,
|
||||
'launch-info': launchInfo,
|
||||
'logo-ohif-small': logoOhifSmall,
|
||||
magnifier: magnifier,
|
||||
'notificationwarning-diamond': notificationwarningDiamond,
|
||||
settings: settings,
|
||||
'sorting-active-down': sortingActiveDown,
|
||||
|
||||
@ -14,7 +14,7 @@ import { ICONS } from './getIcon';
|
||||
|
||||
<Playground>
|
||||
<div className="p-4">
|
||||
<Icon name="active" className="text-custom-aquaBright m-4" />
|
||||
<Icon name="series-active" className="text-custom-aquaBright m-4" />
|
||||
</div>
|
||||
</Playground>
|
||||
|
||||
|
||||
@ -3,10 +3,13 @@
|
||||
{
|
||||
"StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78",
|
||||
"StudyDescription": "BRAIN SELLA",
|
||||
"AccessionNumber": "11788761116031",
|
||||
"StudyDate": "20010108",
|
||||
"StudyTime": "120022",
|
||||
"PatientName": "MISTER^MR",
|
||||
"PatientId": "832040",
|
||||
"Instances": 33,
|
||||
"Modalities": "MR",
|
||||
"series": [
|
||||
{
|
||||
"SeriesDescription": "SAG T-1",
|
||||
|
||||
@ -1,270 +0,0 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
|
||||
/**
|
||||
* Returns a number whose value is limited to the given range.
|
||||
*
|
||||
* @param {number} value The value to be clamped
|
||||
* @param {number} min The lower boundary of the output range
|
||||
* @param {number} max The upper boundary of the output range
|
||||
* @returns {number} A number in the range [min, max]
|
||||
*/
|
||||
function clamp(value, min = 0, max = 1) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (value < min || value > max) {
|
||||
console.error(
|
||||
`@ohif/ui: the value provided ${value} is out of range [${min}, ${max}].`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Math.min(Math.max(min, value), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color from CSS hex format to CSS rgb format.
|
||||
*
|
||||
* @param {string} color - Hex color, i.e. #nnn or #nnnnnn
|
||||
* @returns {string} A CSS rgb color string
|
||||
*/
|
||||
export function hexToRgb(color) {
|
||||
color = color.substr(1);
|
||||
|
||||
const re = new RegExp(`.{1,${color.length / 3}}`, 'g');
|
||||
let colors = color.match(re);
|
||||
|
||||
if (colors && colors[0].length === 1) {
|
||||
colors = colors.map(n => n + n);
|
||||
}
|
||||
|
||||
return colors ? `rgb(${colors.map(n => parseInt(n, 16)).join(', ')})` : '';
|
||||
}
|
||||
|
||||
function intToHex(int) {
|
||||
const hex = int.toString(16);
|
||||
return hex.length === 1 ? `0${hex}` : hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color from CSS rgb format to CSS hex format.
|
||||
*
|
||||
* @param {string} color - RGB color, i.e. rgb(n, n, n)
|
||||
* @returns {string} A CSS rgb color string, i.e. #nnnnnn
|
||||
*/
|
||||
export function rgbToHex(color) {
|
||||
// Idempotent
|
||||
if (color.indexOf('#') === 0) {
|
||||
return color;
|
||||
}
|
||||
|
||||
const { values } = decomposeColor(color);
|
||||
return `#${values.map(n => intToHex(n)).join('')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color from hsl format to rgb format.
|
||||
*
|
||||
* @param {string} color - HSL color values
|
||||
* @returns {string} rgb color values
|
||||
*/
|
||||
export function hslToRgb(color) {
|
||||
color = decomposeColor(color);
|
||||
const { values } = color;
|
||||
const h = values[0];
|
||||
const s = values[1] / 100;
|
||||
const l = values[2] / 100;
|
||||
const a = s * Math.min(l, 1 - l);
|
||||
const f = (n, k = (n + h / 30) % 12) =>
|
||||
l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
|
||||
let type = 'rgb';
|
||||
const rgb = [
|
||||
Math.round(f(0) * 255),
|
||||
Math.round(f(8) * 255),
|
||||
Math.round(f(4) * 255),
|
||||
];
|
||||
|
||||
if (color.type === 'hsla') {
|
||||
type += 'a';
|
||||
rgb.push(values[3]);
|
||||
}
|
||||
|
||||
return recomposeColor({ type, values: rgb });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object with the type and values of a color.
|
||||
*
|
||||
* Note: Does not support rgb % values.
|
||||
*
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @returns {object} - A MUI color object: {type: string, values: number[]}
|
||||
*/
|
||||
export function decomposeColor(color) {
|
||||
// Idempotent
|
||||
if (color.type) {
|
||||
return color;
|
||||
}
|
||||
|
||||
if (color.charAt(0) === '#') {
|
||||
return decomposeColor(hexToRgb(color));
|
||||
}
|
||||
|
||||
const marker = color.indexOf('(');
|
||||
const type = color.substring(0, marker);
|
||||
|
||||
if (['rgb', 'rgba', 'hsl', 'hsla'].indexOf(type) === -1) {
|
||||
throw new Error(
|
||||
[
|
||||
`@ohif/ui: unsupported \`${color}\` color.`,
|
||||
'We support the following formats: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla().',
|
||||
].join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
let values = color.substring(marker + 1, color.length - 1).split(',');
|
||||
values = values.map(value => parseFloat(value));
|
||||
|
||||
return { type, values };
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color object with type and values to a string.
|
||||
*
|
||||
* @param {object} color - Decomposed color
|
||||
* @param {string} color.type - One of: 'rgb', 'rgba', 'hsl', 'hsla'
|
||||
* @param {array} color.values - [n,n,n] or [n,n,n,n]
|
||||
* @returns {string} A CSS color string
|
||||
*/
|
||||
export function recomposeColor(color) {
|
||||
const { type } = color;
|
||||
let { values } = color;
|
||||
|
||||
if (type.indexOf('rgb') !== -1) {
|
||||
// Only convert the first 3 values to int (i.e. not alpha)
|
||||
values = values.map((n, i) => (i < 3 ? parseInt(n, 10) : n));
|
||||
} else if (type.indexOf('hsl') !== -1) {
|
||||
values[1] = `${values[1]}%`;
|
||||
values[2] = `${values[2]}%`;
|
||||
}
|
||||
|
||||
return `${type}(${values.join(', ')})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the contrast ratio between two colors.
|
||||
*
|
||||
* Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
|
||||
*
|
||||
* @param {string} foreground - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @param {string} background - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @returns {number} A contrast ratio value in the range 0 - 21.
|
||||
*/
|
||||
export function getContrastRatio(foreground, background) {
|
||||
const lumA = getLuminance(foreground);
|
||||
const lumB = getLuminance(background);
|
||||
return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
|
||||
}
|
||||
|
||||
/**
|
||||
* The relative brightness of any point in a color space,
|
||||
* normalized to 0 for darkest black and 1 for lightest white.
|
||||
*
|
||||
* Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
|
||||
*
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @returns {number} The relative brightness of the color in the range 0 - 1
|
||||
*/
|
||||
export function getLuminance(color) {
|
||||
color = decomposeColor(color);
|
||||
|
||||
let rgb =
|
||||
color.type === 'hsl'
|
||||
? decomposeColor(hslToRgb(color)).values
|
||||
: color.values;
|
||||
rgb = rgb.map(val => {
|
||||
val /= 255; // normalized
|
||||
return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
|
||||
});
|
||||
|
||||
// Truncate at 3 digits
|
||||
return Number(
|
||||
(0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Darken or lighten a color, depending on its luminance.
|
||||
* Light colors are darkened, dark colors are lightened.
|
||||
*
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @param {number} coefficient=0.15 - multiplier in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function emphasize(color, coefficient = 0.15) {
|
||||
return getLuminance(color) > 0.5
|
||||
? darken(color, coefficient)
|
||||
: lighten(color, coefficient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the absolute transparency of a color.
|
||||
* Any existing alpha values are overwritten.
|
||||
*
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @param {number} value - value to set the alpha channel to in the range 0 -1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function fade(color, value) {
|
||||
color = decomposeColor(color);
|
||||
value = clamp(value);
|
||||
|
||||
if (color.type === 'rgb' || color.type === 'hsl') {
|
||||
color.type += 'a';
|
||||
}
|
||||
color.values[3] = value;
|
||||
|
||||
return recomposeColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Darkens a color.
|
||||
*
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @param {number} coefficient - multiplier in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function darken(color, coefficient) {
|
||||
color = decomposeColor(color);
|
||||
coefficient = clamp(coefficient);
|
||||
|
||||
if (color.type.indexOf('hsl') !== -1) {
|
||||
color.values[2] *= 1 - coefficient;
|
||||
} else if (color.type.indexOf('rgb') !== -1) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
color.values[i] *= 1 - coefficient;
|
||||
}
|
||||
}
|
||||
return recomposeColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightens a color.
|
||||
*
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @param {number} coefficient - multiplier in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function lighten(color, coefficient) {
|
||||
color = decomposeColor(color);
|
||||
coefficient = clamp(coefficient);
|
||||
|
||||
if (color.type.indexOf('hsl') !== -1) {
|
||||
color.values[2] += (100 - color.values[2]) * coefficient;
|
||||
} else if (color.type.indexOf('rgb') !== -1) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
color.values[i] += (255 - color.values[i]) * coefficient;
|
||||
}
|
||||
}
|
||||
|
||||
return recomposeColor(color);
|
||||
}
|
||||
15
platform/ui/src/utils/getInstances.js
Normal file
15
platform/ui/src/utils/getInstances.js
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Calculates the number of instances in series
|
||||
* @param {array} series
|
||||
* @returns {number} Number of instances in series
|
||||
*/
|
||||
|
||||
const getInstances = series => {
|
||||
const instances = series.reduce((acc, item) => {
|
||||
return acc + item.instances.length;
|
||||
}, 0);
|
||||
|
||||
return instances;
|
||||
};
|
||||
|
||||
export default getInstances;
|
||||
17
platform/ui/src/utils/getMockedStudies.js
Normal file
17
platform/ui/src/utils/getMockedStudies.js
Normal file
@ -0,0 +1,17 @@
|
||||
import studyListMock from '../mocks/studyList.json';
|
||||
|
||||
/** Values can be env vars */
|
||||
const DEFAULT_MOCKED_STUDIES_LIMIT = 1000;
|
||||
|
||||
/**
|
||||
* Method to get a mocked study list
|
||||
* @param {number} items Number of studies to be loaded
|
||||
* @returns {array} Study list
|
||||
*/
|
||||
const getMockedStudies = (items = 50) => {
|
||||
const num =
|
||||
items > DEFAULT_MOCKED_STUDIES_LIMIT ? DEFAULT_MOCKED_STUDIES_LIMIT : items;
|
||||
return new Array(num).fill(studyListMock.studies[0]);
|
||||
};
|
||||
|
||||
export default getMockedStudies;
|
||||
20
platform/ui/src/utils/getModalities.js
Normal file
20
platform/ui/src/utils/getModalities.js
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Get formatted Modalities
|
||||
* @param {array} series
|
||||
* @returns {string} Formatted modalities
|
||||
*/
|
||||
const getModalities = series => {
|
||||
const modalities = series.reduce((acc, item) => {
|
||||
const { Modality } = item;
|
||||
if (acc.includes(Modality)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.push(Modality);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return modalities.join('/');
|
||||
};
|
||||
|
||||
export default getModalities;
|
||||
6
platform/ui/src/utils/index.js
Normal file
6
platform/ui/src/utils/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import capitalize from './capitalize';
|
||||
import getMockedStudies from './getMockedStudies';
|
||||
import getModalities from './getModalities';
|
||||
import getInstances from './getInstances';
|
||||
|
||||
export { capitalize, getMockedStudies, getModalities, getInstances };
|
||||
@ -1,18 +1,38 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import Header from './components/Header';
|
||||
import StudyListFilter from './components/StudyListFilter';
|
||||
import StudyListTable from './components/StudyListTable';
|
||||
import StudyListPagination from './components/StudyListPagination';
|
||||
|
||||
const StudyList = () => {
|
||||
const StudyList = ({ studies, perPage }) => {
|
||||
const studiesData = studies.slice(0, perPage);
|
||||
const numOfStudies = studies.length;
|
||||
const isEmptyStudies = numOfStudies === 0;
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={classnames('bg-black h-full', {
|
||||
'h-screen': isEmptyStudies,
|
||||
})}
|
||||
>
|
||||
<Header />
|
||||
<StudyListFilter />
|
||||
<StudyListTable />
|
||||
<StudyListPagination />
|
||||
<StudyListFilter numOfStudies={numOfStudies} />
|
||||
<StudyListTable studies={studiesData} numOfStudies={numOfStudies} />
|
||||
{!isEmptyStudies && <StudyListPagination />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
StudyList.defaultProps = {
|
||||
studies: [],
|
||||
perPage: 25,
|
||||
};
|
||||
|
||||
StudyList.propTypes = {
|
||||
studies: PropTypes.array,
|
||||
perPage: PropTypes.number,
|
||||
};
|
||||
|
||||
export default StudyList;
|
||||
|
||||
@ -4,8 +4,11 @@ menu: Views
|
||||
route: views/studyList
|
||||
---
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Playground, Props } from 'docz';
|
||||
import StudyList from './';
|
||||
import Button from '../../components/Button';
|
||||
import { getMockedStudies } from '../../utils/'
|
||||
|
||||
# StudyList
|
||||
|
||||
@ -20,9 +23,24 @@ import { StudyList } from '@ohfi/ui';
|
||||
## StudyList
|
||||
|
||||
<Playground>
|
||||
<>
|
||||
<StudyList />
|
||||
</>
|
||||
{() => {
|
||||
const [studies, setStudies] = useState([])
|
||||
const handleStudyList = (number) => {
|
||||
const studies = getMockedStudies(number)
|
||||
setStudies(studies)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-white p-4">
|
||||
<Button className="mr-2" onClick={() => handleStudyList(0)}>0 Studies</Button>
|
||||
<Button className="mr-2" onClick={() => handleStudyList(50)}>50 Studies</Button>
|
||||
<Button className="mr-2" onClick={() => handleStudyList(100)}>100 Studies</Button>
|
||||
<Button className="mr-2" onClick={() => handleStudyList(1000)}>1000 Studies</Button>
|
||||
</div>
|
||||
<StudyList studies={studies} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Playground>
|
||||
|
||||
## Properties
|
||||
|
||||
@ -1,19 +1,24 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Button } from '@ohif/ui';
|
||||
import { format } from 'date-fns';
|
||||
import { Button, Icon, Typography } from '@ohif/ui';
|
||||
|
||||
/** TODO: Icon component should be used instead of importing the icons directly */
|
||||
import ChevronRight from '../../../assets/icons/chevron-right.svg';
|
||||
import ChevronDown from '../../../assets/icons/chevron-down.svg';
|
||||
import InstancesActive from '../../../assets/icons/instances-active.svg';
|
||||
import InstancesInactive from '../../../assets/icons/instances-inactive.svg';
|
||||
import LaunchInfo from '../../../assets/icons/launch-info.svg';
|
||||
const TableRow = props => {
|
||||
const {
|
||||
AccessionNumber,
|
||||
Modalities,
|
||||
Instances,
|
||||
StudyDescription,
|
||||
PatientId,
|
||||
PatientName,
|
||||
StudyDate,
|
||||
series,
|
||||
} = props;
|
||||
|
||||
const TableRow = () => {
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
const toggleRow = () => setIsOpened(!isOpened);
|
||||
const ChevronIcon = isOpened ? ChevronDown : ChevronRight;
|
||||
const InstancesIcon = isOpened ? InstancesActive : InstancesInactive;
|
||||
const ChevronIconName = isOpened ? 'chevron-down' : 'chevron-right';
|
||||
const tdClasses = [
|
||||
'px-4 py-2',
|
||||
{ 'border-b border-custom-violetPale': !isOpened },
|
||||
@ -23,7 +28,6 @@ const TableRow = () => {
|
||||
small: 'px-2 flex-0.3',
|
||||
};
|
||||
const seriesBodyClasses = 'border-r border-custom-violetPale';
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr>
|
||||
@ -50,31 +54,39 @@ const TableRow = () => {
|
||||
onClick={toggleRow}
|
||||
>
|
||||
<td className={classnames(...tdClasses)}>
|
||||
<ChevronIcon />
|
||||
<Icon name={ChevronIconName} />
|
||||
</td>
|
||||
<td className={classnames(...tdClasses)}>Patient name</td>
|
||||
<td className={classnames(...tdClasses)}>11000002</td>
|
||||
<td className={classnames(...tdClasses)}>{PatientName}</td>
|
||||
<td className={classnames(...tdClasses)}>{PatientId}</td>
|
||||
<td className={classnames(...tdClasses)}>
|
||||
Mar-29-2013 11:26 AM
|
||||
{format(StudyDate, 'MMM-DD-YYYY')}
|
||||
</td>
|
||||
<td className={classnames(...tdClasses)}>
|
||||
PET^1_PETCT_WB_AC (Adult)
|
||||
{StudyDescription}
|
||||
</td>
|
||||
<td className={classnames(...tdClasses)}>CT/OT/PT</td>
|
||||
<td className={classnames(...tdClasses)}>00000001</td>
|
||||
<td className={classnames(...tdClasses)}>{Modalities}</td>
|
||||
<td className={classnames(...tdClasses)}>
|
||||
<InstancesIcon className="inline-flex mr-2" />
|
||||
902
|
||||
{AccessionNumber}
|
||||
</td>
|
||||
<td className={classnames(...tdClasses)}>
|
||||
<Icon
|
||||
name="series-active"
|
||||
className={classnames('inline-flex mr-2', {
|
||||
'text-custom-blueBright': isOpened,
|
||||
'text-custom-violetPale': !isOpened,
|
||||
})}
|
||||
/>
|
||||
{Instances}
|
||||
</td>
|
||||
</tr>
|
||||
{isOpened && (
|
||||
<tr className={classnames('bg-black')}>
|
||||
<td colSpan="8" className="py-4 pl-20 pr-2">
|
||||
<div>
|
||||
<div className="flex">
|
||||
<Button
|
||||
rounded="full"
|
||||
variant="outlined"
|
||||
endIcon={<LaunchInfo />}
|
||||
endIcon={<Icon name="launch-info" />}
|
||||
className="mr-4"
|
||||
>
|
||||
Basic Viewer
|
||||
@ -82,7 +94,7 @@ const TableRow = () => {
|
||||
<Button
|
||||
rounded="full"
|
||||
variant="outlined"
|
||||
endIcon={<LaunchInfo />}
|
||||
endIcon={<Icon name="launch-info" />}
|
||||
className="mr-4"
|
||||
>
|
||||
Segmentation
|
||||
@ -90,14 +102,17 @@ const TableRow = () => {
|
||||
<Button
|
||||
rounded="full"
|
||||
variant="outlined"
|
||||
endIcon={<LaunchInfo />}
|
||||
endIcon={<Icon name="launch-info" />}
|
||||
>
|
||||
Module 3
|
||||
</Button>
|
||||
<span className="ml-4 text-lg text-custom-grayBright">
|
||||
{/* ADD ICON HERE */}
|
||||
<div className="ml-5 text-lg text-custom-grayBright flex items-center">
|
||||
<Icon
|
||||
name="notificationwarning-diamond"
|
||||
className="mr-2 w-5 h-5"
|
||||
/>
|
||||
Feedback text lorem ipsum dolor sit amet
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div className="w-full text-lg">
|
||||
@ -124,7 +139,7 @@ const TableRow = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 h-48 overflow-y-scroll ohif-scrollbar">
|
||||
{new Array(30).fill('').map((el, i) => (
|
||||
{series.map((seriesItem, i) => (
|
||||
<div className="w-full flex" key={i}>
|
||||
<div
|
||||
className={classnames(
|
||||
@ -140,7 +155,7 @@ const TableRow = () => {
|
||||
seriesBodyClasses
|
||||
)}
|
||||
>
|
||||
#
|
||||
{seriesItem.SeriesNumber}
|
||||
</div>
|
||||
<div
|
||||
className={classnames(
|
||||
@ -148,10 +163,10 @@ const TableRow = () => {
|
||||
seriesBodyClasses
|
||||
)}
|
||||
>
|
||||
CT
|
||||
{seriesItem.Modality}
|
||||
</div>
|
||||
<div className={classnames('pl-3 flex-1')}>
|
||||
149
|
||||
{seriesItem.instances.length}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@ -170,27 +185,85 @@ const TableRow = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const StudyListTable = () => {
|
||||
TableRow.propTypes = {
|
||||
AccessionNumber: PropTypes.string.isRequired,
|
||||
Modalities: PropTypes.string.isRequired,
|
||||
Instances: PropTypes.number.isRequired,
|
||||
PatientId: PropTypes.string.isRequired,
|
||||
PatientName: PropTypes.string.isRequired,
|
||||
StudyDescription: PropTypes.string.isRequired,
|
||||
StudyDate: PropTypes.string.isRequired,
|
||||
series: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
const StudyListTable = ({ studies, numOfStudies }) => {
|
||||
const renderTable = () => {
|
||||
return (
|
||||
<table className="w-full text-white">
|
||||
<tbody>
|
||||
{studies.map((study, i) => (
|
||||
<TableRow
|
||||
key={i}
|
||||
AccessionNumber={study.AccessionNumber || ''}
|
||||
Modalities={study.Modalities || ''}
|
||||
Instances={study.Instances || ''}
|
||||
StudyDescription={study.StudyDescription || ''}
|
||||
PatientId={study.PatientId || ''}
|
||||
PatientName={study.PatientName || ''}
|
||||
StudyDate={study.StudyDate || ''}
|
||||
series={study.series || []}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
const renderEmpty = () => {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center pt-48">
|
||||
<Icon name="magnifier" className="mb-4" />
|
||||
<Typography className="text-custom-aquaBright" variant="h5">
|
||||
No studies available
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="bg-black">
|
||||
<div className="container m-auto relative">
|
||||
<div className="bg-custom-blue text-center text-base py-1 rounded-b sticky top-0">
|
||||
<p className="text-white">
|
||||
Filter list to 100 studies or less to enable sorting
|
||||
</p>
|
||||
</div>
|
||||
<table className="w-full text-white">
|
||||
<tbody>
|
||||
{new Array(30).fill('').map((empty, i) => (
|
||||
<TableRow key={i} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{numOfStudies > 100 && (
|
||||
<div className="bg-custom-blue text-center text-base py-1 rounded-b sticky top-0">
|
||||
<p className="text-white">
|
||||
Filter list to 100 studies or less to enable sorting
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{numOfStudies > 0 && renderTable()}
|
||||
{numOfStudies === 0 && renderEmpty()}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
StudyListTable.propTypes = {
|
||||
studies: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
AccessionNumber: PropTypes.string.isRequired,
|
||||
Modalities: PropTypes.string.isRequired,
|
||||
Instances: PropTypes.number.isRequired,
|
||||
PatientId: PropTypes.string.isRequired,
|
||||
PatientName: PropTypes.string.isRequired,
|
||||
StudyDescription: PropTypes.string.isRequired,
|
||||
StudyDate: PropTypes.string.isRequired,
|
||||
series: PropTypes.array.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
numOfStudies: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default StudyListTable;
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import React from 'react';
|
||||
import { StudyList } from '@ohif/ui';
|
||||
|
||||
// TEMPORARY MOCKING DATA FOR VISUALIZATION PURPOSES
|
||||
import { utils } from '@ohif/ui';
|
||||
|
||||
const ConnectedStudyList = () => {
|
||||
return <StudyList />;
|
||||
const studies = utils.getMockedStudies();
|
||||
return <StudyList studies={studies} />;
|
||||
};
|
||||
|
||||
export default ConnectedStudyList;
|
||||
|
||||
@ -6280,7 +6280,7 @@ date-fns@^1.27.2:
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
|
||||
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
|
||||
|
||||
date-fns@^2.2.1:
|
||||
date-fns@^2.10.0, date-fns@^2.2.1:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.10.0.tgz#abd10604d8bafb0bcbd2ba2e9b0563b922ae4b6b"
|
||||
integrity sha512-EhfEKevYGWhWlZbNeplfhIU/+N+x0iCIx7VzKlXma2EdQyznVlZhCptXUY+BegNpPW2kjdx15Rvq503YcXXrcA==
|
||||
|
||||
Loading…
Reference in New Issue
Block a user