diff --git a/app/controllers/publishers.js b/app/controllers/publishers.js index d53674d..5526472 100644 --- a/app/controllers/publishers.js +++ b/app/controllers/publishers.js @@ -4,7 +4,7 @@ import async from 'async' import _ from 'lodash' import RecordsetPage from 'public/client/js/react/build/recordset' import StatsPage from 'public/client/js/react/build/stats' - +import moment from 'moment' import React from 'react' import ReactDOMServer from 'react-dom/server' @@ -122,7 +122,7 @@ export default { var collected = {}; var taxon = {}; var flags = {}; - var defaultMin = "2015-01-16"; + var defaultMin = moment().subtract(3,'years').startOf('month'); async.parallel([ function(cback) { var params = {"dateInterval": "month", "minDate": defaultMin}; @@ -271,7 +271,7 @@ export default { }); }, function(cback) { - var params = {"dateInterval": "month", "recordset": req.params.id, "minDate": "2015-01-15"}; + var params = {"dateInterval": "month", "recordset": req.params.id, "minDate": moment().subtract(3,'years').startOf('month')}; request.post({"url": config.api + 'summary/stats/search', "json": true, "body": params}, function(a_err, a_resp, a_body) { use = a_body; cback(a_err, 'four'); diff --git a/app/views/base.html b/app/views/base.html index b44a4ac..3640043 100644 --- a/app/views/base.html +++ b/app/views/base.html @@ -55,9 +55,9 @@
-
- × - Take our 30-second survey +
+ × + Take our 30-second survey
The U.S. National Science Foundation and iDigBio are required to collect information on use of digitized collections-based specimen data. Please help us meet this requirement every time you use this search portal. Sustainability of the national digitization effort depends on evidence of data use! Maybe later.
@@ -116,7 +116,7 @@ specimen list
-
diff --git a/app/views/search.html b/app/views/search.html index 73162f6..4ae6fd1 100644 --- a/app/views/search.html +++ b/app/views/search.html @@ -10,10 +10,19 @@ + + {% endblock %} {% block content %} -
+
+
{% endblock %} diff --git a/gulpfile.js b/gulpfile.js index 8fdf0ff..3609bdb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -87,7 +87,7 @@ gulp.task('build', function() { .pipe(buffer()) // load and init sourcemaps .pipe(sourcemaps.init({loadMaps: true})) - // .pipe(uglify()) + .pipe(uglify()) // write sourcemaps .pipe(sourcemaps.write('./')) .pipe(gulp.dest('./public/client/js/react/build')); @@ -106,7 +106,7 @@ gulp.task('libs', function() { .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) // Add transformation tasks to the pipeline here. - // .pipe(uglify()) + .pipe(uglify()) .on('error', log.error) .pipe(sourcemaps.write('./')) .pipe(gulp.dest('./public/js/')); @@ -124,7 +124,7 @@ gulp.task('mapper', function() { .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) // Add transformation tasks to the pipeline here. - // .pipe(uglify()) + .pipe(uglify()) .on('error', log.error) .pipe(sourcemaps.write('./')) .pipe(gulp.dest('./public/js/')); @@ -142,7 +142,7 @@ gulp.task('client', function() { .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) // Add transformation tasks to the pipeline here. - // .pipe(uglify()) + .pipe(uglify()) .on('error', log.error) .pipe(sourcemaps.write('./')) .pipe(gulp.dest('./public/js/')); diff --git a/package.json b/package.json index 96666ff..1f729c5 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@bower_components/leaflet-utfgrid": "danzel/Leaflet.utfgrid", "@bower_components/leaflet.fullscreen": "brunob/leaflet.fullscreen", "@elastic/filesaver": "^1.1.2", - "antd": "^5.15.4", + "antd": "^5.16.1", "async": "^2.0.0", "body-parser": "^1.16.1", "bootstrap": "^3.3.6", diff --git a/public/client/js/lib/dwc_fields.js b/public/client/js/lib/dwc_fields.js index fea573f..b0a0a2a 100644 --- a/public/client/js/lib/dwc_fields.js +++ b/public/client/js/lib/dwc_fields.js @@ -21,6 +21,7 @@ module.exports = { "dwc:taxonRemarks" ], "specimen": [ + "dwc:associatedOccurrences", "dwc:typeStatus", "dwc:identifiedBy", "dwc:dateIdentified", @@ -123,6 +124,18 @@ module.exports = { "dwc:lowestBiostratigraphicZone", "dwc:lithostratigraphicTerms" ], + "idhistory": [ + "dwc:Identification" + ], + "extendedmeasurementorfact": [ + "obis:ExtendedMeasurementOrFact" + ], + "materialsample": [ + "ggbn:MaterialSample" + ], + "chronometricage": [ + "chrono:ChronometricAge" + ], "other": [ "dcterms:language", "dcterms:modified", @@ -476,6 +489,37 @@ module.exports = { "idigbio:institutionName": "Institution Name", "idigbio:collectionCategory": "Collection Category", "idigbio:collectionSize": "Collection Size", - "idigbio:importantHoldings": "Important Holdings" + "idigbio:importantHoldings": "Important Holdings", + "dwc:associatedOccurrences": "Associated Occurrences", + + "idhistory": "Identification History", + "dwc:identificationID": "Identification ID", + "dwc:verbatimIdentification": "Verbatim Identification", + "dwc:identifiedByID": "Identified By (ID)", + "dwc:identificationVerificationStatus": "Identification Verification Status", + + "extendedmeasurementorfact": "Extended Measurement or Fact", + "dwc:measurementDeterminedBy": "Measurement Determined By", + "dwc:measurementDeterminedDate": "Measurement Determined Date", + "dwc:measurementType": "Measurement Type", + "dwc:measurementValue": "Measurement Value", + "obis:measurementTypeID": "Measurement Type ID", + "obis:measurementValueID": "Measurement Value ID", + + "materialsample": "Material Sample", + "ggbn:concentrationUnit": "Concentration Unit", + "ggbn:concentration": "Concentration", + "ggbn:materialSampleType": "Material Sample Type", + "ggbn:ratioOfAbsorbance260_230": "Ratio of Absorbance (260/230 nm)", //| For DNA samples only + "ggbn:ratioOfAbsorbance260_280": "Ratio of Absorbance (260/280 nm)", //| + "ggbn:sampleDesignation": "Sample Designation", + + "chronometricage": "Chronometric Age", + "chrono:chronometricAgeReferences": "Chronometric Age References", + "chrono:chronometricAgeRemarks": "Chronometric Age Remarks", + "chrono:maximumChronometricAge": "Maximum Chronometric Age", + "chrono:maximumChronometricAgeReferenceSystem": "Maximum Chronometric Age Reference System", + "chrono:minimumChronometricAge": "Minimum Chronometric Age", + "chrono:minimumChronometricAgeReferenceSystem": "Minimum Chronometric Age Reference System", } -} \ No newline at end of file +} diff --git a/public/client/js/react/src/record.js b/public/client/js/react/src/record.js index 1cbb00b..e294ad8 100644 --- a/public/client/js/react/src/record.js +++ b/public/client/js/react/src/record.js @@ -7,8 +7,59 @@ import moment from 'moment'; import fields from '../../lib/fields'; import dqFlags from '../../lib/dq_flags'; import idbapi from '../../lib/idbapi'; - - +import { ConfigProvider, Table } from 'antd'; + +const ESO_HIDE_FIELD = -1; +// Sections defined here will be given a table at the end of +// the record page, data section. +// For all fields defined within each section, +// set an integer to denote column ordering, +// where lower integers appear first/left-most. +// Undefined fields will be appended to the last/right-most end of the table. +// Fields set to ESO_HIDE_FIELD will not be displayed in the table. +// +//FIXME// Column sorting might not work properly. +// Example: For specimen where 'occurrence ID' is http://n2t.net/ark:/65665/319944bac-6847-4105-bb85-0cd03f5efad3, +// got column order: [..., Ratio of Absorbance (260/230 nm), Sample Designation, Ratio of Absorbance (260/280 nm)] +// expected 'Sample Designation' to be last +const extendedSpecimenOrder = { + "idhistory" : { + "dwc:scientificName": 1, + "dwc:identifiedBy": 3, + "idigbio:recordID": 6, + "coreid": ESO_HIDE_FIELD, + "dwc:scientificNameAuthorship": 5, + "dwc:dateIdentified": 2, + "dcterms:modified": 7, + }, + "extendedmeasurementorfact": { + "coreid": ESO_HIDE_FIELD, + "dwc:measurementValue": 1, + "dwc:measurementType": 2, + "dwc:measurementDeterminedBy": 3, + "dwc:measurementDeterminedDate": 4, + "obis:measurementValueID": 5, + "obis:measurementTypeID": 6, + }, + "materialsample": { + "coreid": ESO_HIDE_FIELD, + "ggbn:materialSampleType": 1, + "ggbn:concentrationUnit": 2, + "ggbn:concentration": 3, + "ggbn:ratioOfAbsorbance260_230": 4, + "ggbn:ratioOfAbsorance260_280": 5, + "ggbn:sampleDesignation": 6, + }, + "chronometricage": { + "coreid": ESO_HIDE_FIELD, + "chrono:minimumChronometricAge": 1, + "chrono:minimumChronometricAgeReferenceSystem": 2, + "chrono:maximumChronometricAge": 3, + "chrono:maximumChronometricAgeReferenceSystem": 4, + "chrono:chronometricAgeRemarks": 5, + "chrono:chronometricAgeReferences": 6, + }, +} const Row = ({keyid, data}) => { @@ -28,6 +79,12 @@ const Row = ({keyid, data}) => { }; +/** + * @param {object} props + * @param {string} props.name Section ID name, corresponding to dwc_fields.js. + * @param {object} props.data Field key-values specific to this section. + * @param {boolean} props.active If `true`, assign CSS class to allow this section to be visible. + */ const Section = ({name, data, active}) => { var rows = [] @@ -43,10 +100,12 @@ const Section = ({name, data, active}) => { if(active){ cl="section"; } + /** @type {string} */ + let sectionName = dwc.names[name]; return ( -
-
{dwc.names[name]}
- +
+
{sectionName}
+
{rows}
@@ -77,6 +136,11 @@ const Flags = ({flags, active}) => { }; +/** + * @param {object} props + * @param {object} props.record {@linkcode props.raw}, after processing through dwc_fields.js + * @param {object} props.raw Raw search API JSON response for a record + */ const Record = ({record, raw }) => { const [active, setActive] = useState("record") const [nonPropsRecord, setNonPropsRecord] = useState([]) @@ -110,43 +174,122 @@ const Record = ({record, raw }) => { setActive(e.target.attributes['data-tab'].value) } - useEffect(() => { + /** Extracts keys from array of objects. + * + * {@link sec} is used for filtering out columns designated hidden + * (see {@link extendedSpecimenOrder}). + * + * @param {object[]} arr Section data array + * @param {string} sec Section name + */ + function extractKeys(arr, sec) { + return arr.reduce((keys, obj) => { + Object.keys(obj).forEach(key => { + if (!keys.includes(key) && extendedSpecimenOrder[sec][key] != ESO_HIDE_FIELD) { + keys.push(key); + } + }); + return keys; + }, []) + } + + function getAntdColumns(keys, sec) { // takes a list of keys and formats them to be used as antd column headers + const sorted_keys = keys.sort((a, b) => extendedSpecimenOrder[sec][a] - extendedSpecimenOrder[sec][b]) + return sorted_keys.map(key => ({ + title: _.isUndefined(dwc.names[key]) ? key : dwc.names[key], + dataIndex: key, + key: key, + })); + } - var has = []; - var non_props_record = [] - var sorder = ['taxonomy','specimen','collectionevent','locality','paleocontext','other']; - var tabs = [], self = this - var cnt = 0; - - sorder.forEach(function(sec,index){ - if(_.has(record,sec)){ - var active=true; - if(cnt===0){ - active=true; + function completeData(data, keys) { // instantiates missing keys to '' + return data.map((item, index) => { + keys.forEach(key => { + if (!item.hasOwnProperty(key)) { + item[key] = ''; + } + }); + return { ...item, key: index } + }); + } + + /** + * @param {object[]} recordSection Section data array + * @param {string} sec Section name + * @returns Section HTML for the given parameters, including header title + */ + function getAntdTable(recordSection, sec) { + const allKeys = extractKeys(recordSection, sec) + const columns = getAntdColumns(allKeys, sec) + const rows = completeData(recordSection, allKeys) + return (
+
{dwc.names[sec]}
+ + index % 2 === 0 ? 'evenRow' : 'oddRow'} + columns={columns} + dataSource={rows} + scroll={{x: 'max-content'}} + pagination={false} + /> + + ) + } + + useEffect(() => { + console.log('record=', record); + console.log('raw=', raw); + + var has = []; + /** @type {React.JSX.Element[]} */ + var non_props_record = []; + var sorder = ['taxonomy', 'specimen', 'collectionevent', 'locality', 'paleocontext', ...Object.keys(extendedSpecimenOrder), 'other']; + var cnt = 0; + + // Record tab rendering + sorder.forEach(function (sec, index) { + if (_.has(record, sec)) { + var active = true; + if (cnt === 0) { + active = true; + } + if (sec in extendedSpecimenOrder) { + if(!Array.isArray(record[sec])){ + console.error('error creating section \'%s\': not an array', sec); + }else{ + non_props_record.push(getAntdTable(record[sec], sec)) + } + } + else{ + non_props_record.push(
); } - //tabs.push() - non_props_record.push(
); cnt++; } }); setNonPropsRecord([...nonPropsRecord, non_props_record]) - - }, []); + let doRenderFlags = !!raw.indexTerms.flags; + return (
    -
  • Data
  • - {raw.indexTerms.flags ?
  • Flags
  • : ''} -
  • Raw
  • +
  • Data
  • + {doRenderFlags ?
  • Flags
  • : ''} +
  • Raw
-
+
{nonPropsRecord}
- {raw.indexTerms.flags ? : ''} -
+ {doRenderFlags ? : ''} +

@@ -246,6 +389,10 @@ const Citation = ({data, pubname}) => { }; +/** + * @param {object} props + * @param {object} props.record Raw search API JSON response for a record from /view/records/:id + */ const RecordPage = ({ record }) => { const navList = () => { const map = record.indexTerms.geopoint ?
  • Map
  • : null; @@ -322,17 +469,75 @@ const RecordPage = ({ record }) => { _.defaults(canonical, data); _.each(dwc.order, function (val, key) { - _.each(dwc.order[key], function (fld) { + if (key in extendedSpecimenOrder) { + /* Requires special handling to flatten: + * Unlike other sections, this one is an array. + * + * We SHOULD get: + * localRecord: { + * idhistory: [{ + * idfield1_1: ..., + * idfield1_2: ..., + * ... + * },{ + * idfield2_1: ..., + * idfield2_2: ..., + * ... + * }, + * ... + * ] + * } + * Otherwise, with the original implementation under 'else', + * we end up with: + * localRecord: { + * idhistory: [{ + * 'dwc:Identification': [{ + * idfield1_1: ..., + * idfield1_2: ..., + * ... + * },{ + * idfield2_1: ..., + * idfield2_2: ..., + * ... + * }, + * ... + * ] + * }] + * } + */ + const fld = dwc.order[key][0]; + if (dwc.order[key].length > 1) { + // If this soft assert fails, key might correspond to the incorrect DwC field + console.warn("More than one value for dwc_fields order key '%s'. Using first value '%s'.", key, fld); + } if (_.has(canonical, fld)) { if (!_.has(localRecord, key)) { localRecord[key] = []; } - const datum = {}; - datum[fld] = canonical[fld]; - localRecord[key].push(datum); + if (!_.isArray(canonical[fld])) + console.error('error parsing field \'%s\': expected an array', fld); + _.forEach(canonical[fld], function (iden) { + let datum = {}; + _.forIn(iden, function(idenFieldValue, idenFieldName) { + datum[idenFieldName] = idenFieldValue; + }); + localRecord[key].push(datum); + }) has.push(fld); } - }); + } else { + _.each(dwc.order[key], function (fld) { + if (_.has(canonical, fld)) { + if (!_.has(localRecord, key)) { + localRecord[key] = []; + } + let datum = {}; + datum[fld] = canonical[fld]; + localRecord[key].push(datum); + has.push(fld); + } + }); + } }); const dif = _.difference(Object.keys(canonical), has); diff --git a/public/client/js/react/src/search.js b/public/client/js/react/src/search.js index 210d798..1bc1e5b 100644 --- a/public/client/js/react/src/search.js +++ b/public/client/js/react/src/search.js @@ -1,5 +1,5 @@ -import React, {useEffect, useState} from 'react'; +import React, {useEffect, useState, useCallback} from 'react'; import Filters, {defaultFilters, newFilterProps} from './search/filters' import Sorting, {defaultSorts} from './search/sorting' import Mapping from './search/mapping' @@ -16,6 +16,19 @@ const Search = () => { const [resultsTab, setResultsTab] = useState('list') const [search, setSearch] = useState(defaultSearch()) const [aggs, setAggs] = useState([]) + + useEffect(() => { + // Hide the loader when the component is mounted + const loader = document.getElementById('loader'); + const main = document.getElementById('main') + if (loader) { + loader.style.display = 'none'; + } + if (main) { + main.style.height='auto' + } + }, []); + function defaultSearch(){ return { filters: defaultFilters(), @@ -71,34 +84,36 @@ const Search = () => { searchHistory.push(currentSearch); // Update the state with 'search' setSearch(currentSearch) + // setReady(true) }, []); - function searchChange(key,val){ + + const searchChange = useCallback((key, val) => { + console.log(key, val); var newSearch = _.cloneDeep(search); - if(typeof key == 'string'){ - newSearch[key]=val; - }else if(typeof key == 'object'){ - _.each(key,function(v,k){ - newSearch[k]=v; + if (typeof key === 'string') { + newSearch[key] = val; + } else if (typeof key === 'object') { + _.each(key, function (v, k) { + newSearch[k] = v; }); } - setSearch(newSearch) - // setHistory([...history, search]) - searchHistory.push(newSearch); - } + setSearch(newSearch); + searchHistory.push(newSearch); // Assuming searchHistory can handle reactivity properly + }, [search, setSearch, searchHistory]); - function viewChange(view,option){ - //currently only supports options panel and results tabs - if(view=='optionsTab'){ + const viewChange = useCallback((view, option) => { + if (view === 'optionsTab') { localStorage.setItem(view, option); - setOptionsTab(option) - } else if (view=='resultsTab') { + setOptionsTab(option); + } else if (view === 'resultsTab') { localStorage.setItem(view, option); - setResultsTab(option) + setResultsTab(option); } - } + }, [setOptionsTab, setResultsTab]); // Not including these in deps if they are from useState - return( + + return (
    ); -} +}) var sortClick=false; const ResultsList = ({search, searchChange, results, loading}) => { const [columns, setColumnsState] = useState(defaultColumns()) @@ -135,7 +138,7 @@ const ResultsList = ({search, searchChange, results, loading}) => { }, []) function resetColumns(){ - setColumns(defaultColumns()); + setColumns(defaultColumns()); } function defaultColumns(){ return ['family','scientificname','datecollected','country','institutioncode','basisofrecord']; @@ -169,7 +172,7 @@ const ResultsList = ({search, searchChange, results, loading}) => { //sorted column sorts the top level sort value in search and new sorting items length //shall not exceed original length var dir, localSearch = _.cloneDeep(search), name=e.currentTarget.attributes['data-term'].value, - sort={name: name}, sorting=localSearch.sorting, curlength = sorting.length; + sort={name: name}, sorting=localSearch.sorting, curlength = sorting.length; if(_.isUndefined(e.currentTarget.attributes['data-sort'])){ dir='asc'; }else{ @@ -198,14 +201,14 @@ const ResultsList = ({search, searchChange, results, loading}) => { //to prevent opening if hiliting text if(window.getSelection().toString().length===0 || (e.target.nodeName=='I' || e.target.nodeName=='BUTTON')){ - window.open('/portal/records/'+e.currentTarget.id,e.currentTarget.id); + window.open('/portal/records/'+e.currentTarget.id,e.currentTarget.id); } } var cols = columns,self=this; - //['scientificname','genus','collectioncode','specificepithet','commonname']; + //['scientificname','genus','collectioncode','specificepithet','commonname']; var rows=[]; var headers=[]; //results table @@ -331,7 +334,7 @@ const ResultsList = ({search, searchChange, results, loading}) => {
    - {list} + {list}
    @@ -343,10 +346,10 @@ const ResultsList = ({search, searchChange, results, loading}) => {
    - {headers} + {headers} - {rows} + {rows}
    @@ -446,25 +449,25 @@ class ResultListColumnSelector extends React.Component{ } }); fgroups.push( - -   {fltrs} - + +   {fltrs} + ); }); var updisabled = ( ind === 0 ); var downdisabled = ( ind === self.state.columns.length-1 ); selects.push(
    -
    - - -
    - - +
    + + +
    + +
    ); }); @@ -537,7 +540,7 @@ const ResultsLabels = ({results, loading, stamp}) => { content.push({formatedDC}); } - var l=[]; + var l=[]; ['dwc:country','dwc:stateProvince','dwc:county','dwc:locality'].forEach(function(item){ if(_.has(raw,item)){ l.push(raw[item]) @@ -657,52 +660,59 @@ const ResultsLabels = ({results, loading, stamp}) => { }; -const ResultsImages = ({loadingProp, resultsProp, search}) => { +const ResultsImages = memo(({loadingProp, resultsProp, search}) => { const [results, setResults] = useState(resultsProp) const [loading, setLoading] = useState(loadingProp) - function getImageOnlyResults(search){ - - var d = new Date, self=this, searchState = _.cloneDeep(search); - searchState.image=true; + // useEffect(() => { + // setResults(resultsProp) + // }, [resultsProp]); + const getImageOnlyResults = (search) => { + console.log(search) + var d = new Date(); + var self = this; + var searchState = _.cloneDeep(search); + searchState.image = true; var query = queryBuilder.makeSearchQuery(searchState); var now = d.getTime(); let lastQueryTime = now; - setLoading(true) - idbapi.search(query,function(response){ - //make sure last query run is the last one that renders - //as responses can be out of order - if(now>= lastQueryTime){ + setLoading(true); + idbapi.search(query, function(response) { + // Make sure last query run is the last one that renders + // As responses can be out of order + if (now >= lastQueryTime) { var res; - if(searchState.from > 0){ + if (searchState.from > 0) { res = results.concat(response.items); - }else{ + } else { res = response.items; } - setResults(res) - setLoading(false) - // self.setState({results: res, loading: false},function(){ + setResults(res); + setLoading(false); + // self.setState({results: res, loading: false}, function(){ // self.forceUpdate(); // }); } }); - } + }; + function errorImage(e){ e.target.attributes['src'].value = '/portal/img/missing.svg'; } - function componentDidMount(){ - if(!search.image){ - getImageOnlyResults(search); - } - } + // function componentDidMount(){ + // if(!search.image){ + // getImageOnlyResults(search); + // } + // } useEffect(() => { // if(search.image) { // setResults(search.results); // setLoading(false); // } else { - getImageOnlyResults(search); + getImageOnlyResults(search); // } }, [search]); + // function UNSAFE_componentWillReceiveProps(nextProps){ // if(nextProps.search.image){ // setResults(nextProps.results) @@ -731,8 +741,8 @@ const ResultsImages = ({loadingProp, resultsProp, search}) => { {count} {name.join(' + src={idbapi.media_host + "v2/media/"+uuid+"?size=thumbnail"} + onError={errorImage}/>
    {_.capitalize(name.join(' '))} @@ -752,6 +762,7 @@ const ResultsImages = ({loadingProp, resultsProp, search}) => { }) } }); + if(images.length === 0 && !loading){ images.push(
    @@ -774,7 +785,7 @@ const ResultsImages = ({loadingProp, resultsProp, search}) => {
    ) -}; +}); const Providers = ({attribution}) => { @@ -792,10 +803,10 @@ const Providers = ({attribution}) => {
    - + - {list} + {list}
    RecordsetRecords in resultsDescription
    RecordsetRecords in resultsDescription
    diff --git a/public/client/js/react/src/shared/stats_charts.js b/public/client/js/react/src/shared/stats_charts.js index be44d67..3ca29a8 100644 --- a/public/client/js/react/src/shared/stats_charts.js +++ b/public/client/js/react/src/shared/stats_charts.js @@ -296,7 +296,7 @@ const TaxonPies = ({data}) => { } const StatsCharts = (props) => { - const [startDate, setStartDate] = useState(moment("2015-01-16", "YYYY-MM-DD")); + const [startDate, setStartDate] = useState(moment().subtract(3,'years').startOf('month')); const [endDate, setEndDate] = useState(moment().subtract(1, 'months').endOf('month')); const [log, setLog] = useState(true); const [cumulative, setCumulative] = useState(true); diff --git a/public/client/js/react/src/stats.js b/public/client/js/react/src/stats.js index 3345c8a..9611cd1 100644 --- a/public/client/js/react/src/stats.js +++ b/public/client/js/react/src/stats.js @@ -25,9 +25,14 @@ const Stats = ({usage, ingest, ingestCumulative, collected, taxon, flags}) => {
    diff --git a/public/client/less/idigbio.less b/public/client/less/idigbio.less index d0cf1bf..26c53f6 100644 --- a/public/client/less/idigbio.less +++ b/public/client/less/idigbio.less @@ -484,7 +484,7 @@ a:hover{ -#footer { border-top: 1px solid #ccc; padding-top: 15px; margin-top: 20px; } +#footer { border-top: 1px solid #ccc; padding-top: 15px; margin-top: 20px; height: auto; } #footer p { font-size: 13px; line-height: 15px; margin-bottom: 5px; color: #777; } #footnote { border-top: 1px solid #ccc; padding-top: 10px; padding-bottom: 15px } diff --git a/public/client/less/record.less b/public/client/less/record.less index 5a3f219..3859d64 100644 --- a/public/client/less/record.less +++ b/public/client/less/record.less @@ -86,7 +86,7 @@ h1#banner{ } } .sec{ - font-size: 13px; + font-size: @data-font-size; table{ td{ padding: 1px 0px; @@ -106,11 +106,72 @@ h1#banner{ } } +// Overrides for antd default table styling +.custom-antd-border() { + border: 1px solid #ddd; /* Solid gray border */ +} +.custom-antd-cell-properties() { + //corresponds to: #bootstrap .table-condensed tr {th,td} + padding: 5px; + + //corresponds to: #bootstrap .table-bordered tr {th,td} + .custom-antd-border(); +} +.custom-antd-table { + &.ant-table-wrapper table { + //from: #bootstrap.table() + margin-bottom: 20px; + + //corresponds to: #bootstrap.table-bordered(); + .custom-antd-border(); + + //corresponds to: #bootstrap.table(); + border-collapse: collapse; + } + + [class^='ant-table'], [class*='ant-table'] { + font-size: @data-font-size; + + //from: #bootstrap.body(); + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + } + + .ant-table-thead > tr > th { + .custom-antd-cell-properties(); + } + .ant-table-tbody > tr > td { + .custom-antd-cell-properties(); + } + + .ant-table { + color: @data-text-color; + } + + .ant-table-tbody > tr.oddRow > td { + //corresponds to: #bootstrap > .table-striped > tbody > tr:nth-of-type(odd): + background-color: #f9f9f9; /* Light grey background for odd rows */ + } + + .ant-table-tbody > tr.evenRow > td { + background-color: #ffffff; /* White background for even rows */ + } + + .ant-table-thead > tr > th { + border-radius: 0; + border-bottom-width: 2px; + } +} +.ant-table { + border-radius: 0 !important; +} + +@data-text-color: #555; +@data-font-size: 13px; #data{ margin-top:30px; #record, #flags{ border-top:@brd; - font-size:13px; + font-size:@data-font-size; padding-top:20px; .field-name{ font-weight:normal; @@ -123,7 +184,7 @@ h1#banner{ word-break:break-word; overflow:hidden; white-space: pre-line; - color:#555; + color:@data-text-color; } } @import 'tabs.less'; diff --git a/yarn.lock b/yarn.lock index 5641380..51e7a1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,9 +18,9 @@ "@ctrl/tinycolor" "^3.6.1" "@ant-design/cssinjs@^1.18.5": - version "1.18.5" - resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-1.18.5.tgz#5185c2c40468b82959ea27a99b0b4617e181d1ce" - integrity sha512-Ub4n3d+MAX/qtE5S9PM8iOn5ocU7GUAIC4Adc2X8UCMXnsRRfpJBHsBdtQ1qoAuaQ7lU2M1BTCuJ+fkv4fOWiw== + version "1.19.1" + resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-1.19.1.tgz#d3bb4f58ee884c9c757688e611e7a6de9867ea75" + integrity sha512-hgQ3wiys3X0sqDKWkqCJ6EYdF79i9JCvtavmIGwuuPUKmoJXV8Ff0sY+yQQSxk2dRmMyam/bYKo/Bwor45hnZw== dependencies: "@babel/runtime" "^7.11.1" "@emotion/hash" "^0.8.0" @@ -35,10 +35,10 @@ resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz#ed2be7fb4d82ac7e1d45a54a5b06d6cecf8be6f6" integrity sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA== -"@ant-design/icons@^5.3.5": - version "5.3.5" - resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-5.3.5.tgz#122d51c81a4e12d6926c0af3f38ade281ba7030c" - integrity sha512-Vyv/OsKz56BsKBtcRlLP6G8RGaRW43f7G5dK3XNPCaeV4YyehLVaITuNKi2YJG9hMVURkBdzdGhveNQlnKTFqw== +"@ant-design/icons@^5.3.6": + version "5.3.6" + resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-5.3.6.tgz#172bbcfcfa39f9a1d9ef73ad5cf98c6184fb83f8" + integrity sha512-JeWsgNjvkTTC73YDPgWOgdScRku/iHN9JU0qk39OSEmJSCiRghQMLlxGTCY5ovbRRoXjxHXnUKgQEgBDnQfKmA== dependencies: "@ant-design/colors" "^7.0.0" "@ant-design/icons-svg" "^4.4.0" @@ -46,10 +46,10 @@ classnames "^2.2.6" rc-util "^5.31.1" -"@ant-design/react-slick@~1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-1.0.2.tgz#241bb412aeacf7ff5d50c61fa5db66773fde6b56" - integrity sha512-Wj8onxL/T8KQLFFiCA4t8eIRGpRR+UPgOdac2sYzonv+i0n3kXHmvHLLiOYL655DQx2Umii9Y9nNgL7ssu5haQ== +"@ant-design/react-slick@~1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-1.1.2.tgz#f84ce3e4d0dc941f02b16f1d1d6d7a371ffbb4f1" + integrity sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA== dependencies: "@babel/runtime" "^7.10.4" classnames "^2.2.5" @@ -1045,10 +1045,10 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" - integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== +"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" + integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== dependencies: regenerator-runtime "^0.14.0" @@ -1384,10 +1384,10 @@ classnames "^2.3.2" rc-util "^5.24.4" -"@rc-component/trigger@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-2.0.0.tgz#f3d63daea1b734ffe9cbf2e9403fae523d449a8a" - integrity sha512-niwKADPdY5dhdIblV6uwSayVivwo2uUISfJqri+/ovYQcH/omxDYBJKo755QKeoIIsWptxnRpgr7reEnNEZGFg== +"@rc-component/trigger@^2.0.0", "@rc-component/trigger@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-2.1.1.tgz#47973f1156ba63810c913eb46cbaedeba913874b" + integrity sha512-UjHkedkgtEcgQu87w1VuWug1idoDJV7VUt0swxHXRcmei2uu1AuUzGBPEUlmOmXGJ+YtTgZfVLi7kuAUKoZTMA== dependencies: "@babel/runtime" "^7.23.2" "@rc-component/portal" "^1.1.0" @@ -1751,16 +1751,16 @@ ansistyles@~0.1.3: resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539" integrity sha512-6QWEyvMgIXX0eO972y7YPBLSBsq7UWKFAoNNTLGaOJ9bstcEL9sCbcjf96dVfNDdUsRoGOK82vWFJlKApXds7g== -antd@^5.15.4: - version "5.15.4" - resolved "https://registry.yarnpkg.com/antd/-/antd-5.15.4.tgz#93b7fea7e3bad857a6ed1d2c0407ca592edc8a35" - integrity sha512-79eLOQW1DG92yzulx+ValfHFjvPnaaI41BffGquAnzx42Ws3eEcKofsa2jNRyJN5NWr9I5wqvABDq9rRRfGGsg== +antd@^5.16.1: + version "5.16.1" + resolved "https://registry.yarnpkg.com/antd/-/antd-5.16.1.tgz#a258e73cac8c6e63c4ec44588e5474f0403cd33a" + integrity sha512-XAlLRrgYV+nj9FHnkXEPS6HNcKcluEa8v44e7Cixjlp8aOXRhUI6IfZaKpc2MPGjQ+06rp62/dsxOUNJW9kfLA== dependencies: "@ant-design/colors" "^7.0.2" "@ant-design/cssinjs" "^1.18.5" - "@ant-design/icons" "^5.3.5" - "@ant-design/react-slick" "~1.0.2" - "@babel/runtime" "^7.24.1" + "@ant-design/icons" "^5.3.6" + "@ant-design/react-slick" "~1.1.2" + "@babel/runtime" "^7.24.4" "@ctrl/tinycolor" "^3.6.1" "@rc-component/color-picker" "~1.5.3" "@rc-component/mutate-observer" "^1.1.0" @@ -1776,17 +1776,17 @@ antd@^5.15.4: rc-dialog "~9.4.0" rc-drawer "~7.1.0" rc-dropdown "~4.2.0" - rc-field-form "~1.42.1" + rc-field-form "~1.44.0" rc-image "~7.6.0" rc-input "~1.4.5" rc-input-number "~9.0.0" rc-mentions "~2.11.1" rc-menu "~9.13.0" rc-motion "^2.9.0" - rc-notification "~5.3.0" + rc-notification "~5.4.0" rc-pagination "~4.0.4" rc-picker "~4.3.0" - rc-progress "~3.5.1" + rc-progress "~4.0.0" rc-rate "~2.12.0" rc-resize-observer "^1.4.0" rc-segmented "~2.3.0" @@ -1794,7 +1794,7 @@ antd@^5.15.4: rc-slider "~10.5.0" rc-steps "~6.0.1" rc-switch "~4.1.0" - rc-table "~7.42.0" + rc-table "~7.45.4" rc-tabs "~14.1.1" rc-textarea "~1.6.3" rc-tooltip "~6.2.0" @@ -9064,9 +9064,9 @@ raw-body@2.5.2: unpipe "1.0.0" rc-cascader@~3.24.0: - version "3.24.0" - resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.24.0.tgz#f43f06233b89b7b46005c19f3233068c5b379b24" - integrity sha512-NwkYsVULA61S085jbOYbq8Z7leyIxVmLwf+71mWLjA3kCfUf/rAKC0WfjQbqBDaLGlU9d4z1EzyPaHBKLYWv6A== + version "3.24.1" + resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.24.1.tgz#603bac4427f8865b6ec056705bae8485c65d7798" + integrity sha512-RgKuYgEGPx+6wCgguYFHjMsDZdCyydZd58YJRCfYQ8FObqLnZW0x/vUcEyPjhWIj1EhjV958IcR+NFPDbbj9kg== dependencies: "@babel/runtime" "^7.12.5" array-tree-filter "^2.1.0" @@ -9126,10 +9126,10 @@ rc-dropdown@~4.2.0: classnames "^2.2.6" rc-util "^5.17.0" -rc-field-form@~1.42.1: - version "1.42.1" - resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-1.42.1.tgz#70a1c479a1c011b28375bd129d2e9366ed0a1757" - integrity sha512-SqiEmWNP+I61Lt80+ofPvT+3l8Ij6vb35IS+x14gheVnCJN0SRnOwEgsqCEB5FslT7xqjUqDnU845hRZ1jzlAA== +rc-field-form@~1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-1.44.0.tgz#a66548790fbcee8c5432e9f2efcd1b46b090984b" + integrity sha512-el7w87fyDUsca63Y/s8qJcq9kNkf/J5h+iTdqG5WsSHLH0e6Usl7QuYSmSVzJMgtp40mOVZIY/W/QP9zwrp1FA== dependencies: "@babel/runtime" "^7.18.0" async-validator "^4.1.0" @@ -9201,10 +9201,10 @@ rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motio classnames "^2.2.1" rc-util "^5.21.0" -rc-notification@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-5.3.0.tgz#e31c86fe2350598ade8cff383babd1befa7a94fe" - integrity sha512-WCf0uCOkZ3HGfF0p1H4Sgt7aWfipxORWTPp7o6prA3vxwtWhtug3GfpYls1pnBp4WA+j8vGIi5c2/hQRpGzPcQ== +rc-notification@~5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-5.4.0.tgz#c5ea20bfe4ed2dbacc7ef945777626c050945db8" + integrity sha512-li19y9RoYJciF3WRFvD+DvWS70jdL8Fr+Gfb/OshK+iY6iTkwzoigmSIp76/kWh5tF5i/i9im12X3nsF85GYdA== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" @@ -9231,9 +9231,9 @@ rc-pagination@~4.0.4: rc-util "^5.38.0" rc-picker@~4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-4.3.0.tgz#920e89093d0f7e31443718ad822dd97118ad84f4" - integrity sha512-bQNB/+NdW55jlQ5lPnNqF5J90Tq4SihLbAF7tzPBvGDJyoYmDgwLm4FN0ZB3Ot9i1v6vJY/1mgqZZTT9jbYc5w== + version "4.3.2" + resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-4.3.2.tgz#b62e02122b3b6043a5a347674e5d003b8e51873e" + integrity sha512-2NtobLxG2YqllXn4YczbupgIH6PSqzjCfFCnGlgPIY9k0HZti8WmBPjS1OD9JKQl+Tdg0pMVUeTEc07y4X9ZRQ== dependencies: "@babel/runtime" "^7.10.1" "@rc-component/trigger" "^2.0.0" @@ -9242,10 +9242,10 @@ rc-picker@~4.3.0: rc-resize-observer "^1.4.0" rc-util "^5.38.1" -rc-progress@~3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-3.5.1.tgz#a3cdfd2fe04eb5c3d43fa1c69e7dd70c73b102ae" - integrity sha512-V6Amx6SbLRwPin/oD+k1vbPrO8+9Qf8zW1T8A7o83HdNafEVvAxPV5YsgtKFP+Ud5HghLj33zKOcEHrcrUGkfw== +rc-progress@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-4.0.0.tgz#5382147d9add33d3a5fbd264001373df6440e126" + integrity sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.6" @@ -9281,12 +9281,12 @@ rc-segmented@~2.3.0: rc-util "^5.17.0" rc-select@~14.13.0: - version "14.13.0" - resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.13.0.tgz#5f343d18398833e0fe44a060a17a8dc1775cb650" - integrity sha512-ew34FsaqHokK4dxVrcIxSYrgWJ2XJYlkk32eiOIiEo3GkHUExdCzmozMYaUc2P67c5QJRUvvY0uqCs3QG67h5A== + version "14.13.1" + resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.13.1.tgz#2d3ca539032bf8184e511597bb93436e5b9cf02c" + integrity sha512-A1VHqjIOemxLnUGRxLGVqXBs8jGcJemI5NXxOJwU5PQc1wigAu1T4PRLgMkTPDOz8gPhlY9dwsPzMgakMc2QjQ== dependencies: "@babel/runtime" "^7.10.1" - "@rc-component/trigger" "^2.0.0" + "@rc-component/trigger" "^2.1.1" classnames "2.x" rc-motion "^2.0.1" rc-overflow "^1.3.1" @@ -9320,10 +9320,10 @@ rc-switch@~4.1.0: classnames "^2.2.1" rc-util "^5.30.0" -rc-table@~7.42.0: - version "7.42.0" - resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.42.0.tgz#94a2ce0c76b38336ed04a69587ad264bfc533805" - integrity sha512-GwHV9Zs3HvWxBkoXatO/IeKoElzy3Ojf3dcyw1Rj3cyQVb+ZHtexslKdyzsrKRPJ0mUa62BoX+ZAg3zgTEql8w== +rc-table@~7.45.4: + version "7.45.4" + resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.45.4.tgz#8657450d836f691e94ff2282a1c8ee344fe551e6" + integrity sha512-6aSbGrnkN2GLSt3s1x+wa4f3j/VEgg1uKPpaLY5qHH1/nFyreS2V7DFJ0TfUb18allf2FQl7oVYEjTixlBXEyQ== dependencies: "@babel/runtime" "^7.10.1" "@rc-component/context" "^1.4.0"