diff --git a/src/renderer/src/components/Icons.jsx b/src/renderer/src/components/Icons.jsx new file mode 100644 index 0000000..20d4a72 --- /dev/null +++ b/src/renderer/src/components/Icons.jsx @@ -0,0 +1,58 @@ +export const MicroChevronLeft = () => ( + + + +) + +export const MicroChevronRight = () => ( + + + +) + +export const MicroArrowDownTray = () => ( + + + + +) + +export const MicroTrash = () => ( + + + +) + +export const MicroArrowTopRight = () => ( + + + + +) diff --git a/src/renderer/src/components/Pagination.jsx b/src/renderer/src/components/Pagination.jsx new file mode 100644 index 0000000..21296b0 --- /dev/null +++ b/src/renderer/src/components/Pagination.jsx @@ -0,0 +1,75 @@ +import React from 'react' +import { MicroChevronLeft, MicroChevronRight } from './Icons' + +const Pagination = ({ + currentPage, + totalPages, + onPageChange, + onItemsPerPageChange, + onJumpToPageSubmit, + jumpToPage, + setJumpToPage, + itemsPerPage +}) => ( +
+
+
+ + setJumpToPage(e.target.value)} + className="px-2 py-1 border border-gray-300 rounded" + /> + +
+
+
+
+ + +
+ + Page {currentPage} of {totalPages} + +
+ +
+
+ +
+
+
+) + +export default Pagination diff --git a/src/renderer/src/output.css b/src/renderer/src/output.css index 5958b1b..fb1887f 100644 --- a/src/renderer/src/output.css +++ b/src/renderer/src/output.css @@ -636,6 +636,16 @@ html { z-index: 50; } +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.mx-4 { + margin-left: 1rem; + margin-right: 1rem; +} + .mx-auto { margin-left: auto; margin-right: auto; @@ -755,14 +765,14 @@ html { width: 50%; } -.w-1\/3 { - width: 33.333333%; -} - .w-4 { width: 1rem; } +.w-4\/5 { + width: 80%; +} + .w-48 { width: 12rem; } @@ -855,12 +865,6 @@ html { margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); } -.space-x-4 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(1rem * var(--tw-space-x-reverse)); - margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); -} - .space-y-2 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); @@ -885,10 +889,6 @@ html { overflow-y: auto; } -.overflow-y-scroll { - overflow-y: scroll; -} - .whitespace-normal { white-space: normal; } @@ -897,6 +897,10 @@ html { white-space: pre-wrap; } +.break-words { + overflow-wrap: break-word; +} + .rounded { border-radius: 0.25rem; } @@ -913,6 +917,11 @@ html { border-radius: 0.375rem; } +.rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + .border { border-width: 1px; } @@ -933,6 +942,11 @@ html { border-right-width: 1px; } +.border-gray-200 { + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity)); +} + .border-gray-300 { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); @@ -1262,6 +1276,10 @@ html { /* Chrome, Safari and Opera */ +.\[word-break\:break-word\] { + word-break: break-word; +} + @keyframes spin { to { transform: rotate(360deg); @@ -1308,11 +1326,6 @@ html { background-color: rgb(249 250 251 / var(--tw-bg-opacity)); } -.hover\:bg-gray-600:hover { - --tw-bg-opacity: 1; - background-color: rgb(75 85 99 / var(--tw-bg-opacity)); -} - .hover\:bg-red-600:hover { --tw-bg-opacity: 1; background-color: rgb(220 38 38 / var(--tw-bg-opacity)); @@ -1365,9 +1378,21 @@ html { } @media (min-width: 768px) { + .md\:mt-0 { + margin-top: 0px; + } + .md\:w-1\/2 { width: 50%; } + + .md\:w-1\/3 { + width: 33.333333%; + } + + .md\:flex-row { + flex-direction: row; + } } @media (min-width: 1024px) { diff --git a/src/renderer/src/pages/DataDictionary.jsx b/src/renderer/src/pages/DataDictionary.jsx index c4ab7d9..5fade88 100644 --- a/src/renderer/src/pages/DataDictionary.jsx +++ b/src/renderer/src/pages/DataDictionary.jsx @@ -3,7 +3,8 @@ import { useRef, useState, useEffect, useCallback, useMemo } from 'react' import { objectToCsv } from '../utils/downloadUtils' import { updateFormulaNames } from '../utils/helpers' import ExportLink from '../components/ExportLink' -import { triggerLoading } from '../reducers/statusReducer' +import { MicroChevronLeft, MicroChevronRight } from '../components/Icons' +import Pagination from '../components/Pagination' // eslint-disable-next-line react/prop-types const DataDictionary = ({ dictionaryDb }) => { @@ -14,7 +15,8 @@ const DataDictionary = ({ dictionaryDb }) => { const [searchQuery, setSearchQuery] = useState('') const [searchInput, setSearchInput] = useState('') const dictionaryDbRef = useRef(dictionaryDb) - const allElements = useLiveQuery(() => dictionaryDbRef.current.elements.toArray(), []) || [] + const allElements = + useLiveQuery(() => dictionaryDbRef.current.elements.orderBy('category').toArray(), []) || [] const data = useMemo(() => allElements, [allElements]) @@ -58,10 +60,6 @@ const DataDictionary = ({ dictionaryDb }) => { setCurrentPage(1) // Reset to first page when items per page changes } - const handleJumpToPageChange = (event) => { - setJumpToPage(event.target.value) - } - const handleJumpToPageSubmit = (event) => { event.preventDefault() const page = Number(jumpToPage) @@ -126,7 +124,7 @@ const DataDictionary = ({ dictionaryDb }) => {
- + @@ -140,75 +138,32 @@ const DataDictionary = ({ dictionaryDb }) => { {currentPageData.map((el) => ( - - - - - - - - + + + + + + + + ))}
Type JSON ID Name
{el.category}{el.id}{el.displayName}{el.displayDescription}{el.numerator}{el.numeratorName}{el.denominator}{el.denominatorName}{el.category}{el.id}{el.displayName}{el.displayDescription}{el.numerator}{el.numeratorName}{el.denominator} + {el.denominatorName} +
)} -
- - - Page {currentPage} of {totalPages} - - -
-
-
- - -
-
-
- - - -
-
-
+
diff --git a/src/renderer/src/pages/DownloadHistory/index.jsx b/src/renderer/src/pages/DownloadHistory/index.jsx index 9cf35f7..dd545db 100644 --- a/src/renderer/src/pages/DownloadHistory/index.jsx +++ b/src/renderer/src/pages/DownloadHistory/index.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useMemo } from 'react' import { useDispatch, useSelector } from 'react-redux' import JSZip from 'jszip' import { useLiveQuery } from 'dexie-react-hooks' @@ -12,9 +12,15 @@ import { clearEditedRow } from '../../reducers/historyReducer' import { addSelectedElements } from '../../reducers/dataElementsReducer' +import { updateOrgUnitLevels, toggleOrgUnitSelection } from '../../reducers/orgUnitReducer' +import { MicroArrowDownTray, MicroArrowTopRight, MicroTrash } from '../../components/Icons' +import Pagination from '../../components/Pagination' // eslint-disable-next-line react/prop-types const HistoryPage = ({ dictionaryDb, queryDb }) => { + const [currentPage, setCurrentPage] = useState(1) + const [jumpToPage, setJumpToPage] = useState('') + const [itemsPerPage, setItemsPerPage] = useState(10) const downloadQueries = useLiveQuery(() => queryDb.query.toArray(), []) || [] const [temporaryNote, setTemporaryNote] = useState('') const dispatch = useDispatch() @@ -23,6 +29,35 @@ const HistoryPage = ({ dictionaryDb, queryDb }) => { const worker = new Worker(new URL('../../service/worker.js', import.meta.url)) const allElements = useLiveQuery(() => dictionaryDb.elements.toArray(), []) || [] + const totalPages = useMemo(() => { + return Math.ceil(downloadQueries.length / itemsPerPage) + }, [downloadQueries.length, itemsPerPage]) + + const currentPageData = useMemo(() => { + const startIndex = (currentPage - 1) * itemsPerPage + const endIndex = startIndex + itemsPerPage + return downloadQueries.slice(startIndex, endIndex) + }, [currentPage, itemsPerPage, downloadQueries]) + + useEffect(() => {}, [currentPageData]) + + const handlePageChange = (page) => { + setCurrentPage(page) + } + + const handleItemsPerPageChange = (event) => { + setItemsPerPage(Number(event.target.value)) + setCurrentPage(1) + } + + const handleJumpToPageSubmit = (event) => { + event.preventDefault() + const page = Number(jumpToPage) + if (page > 0 && page <= totalPages) { + setCurrentPage(page) + } + } + useEffect(() => { if (editedRow.rowId !== null) { setTemporaryNote(editedRow.note || '') @@ -143,12 +178,29 @@ const HistoryPage = ({ dictionaryDb, queryDb }) => { } const handlePassParams = (id) => { - console.log(downloadQueries) const params = downloadQueries.filter((el) => el.id === id) const dimensions = params.flatMap((param) => param.dimension.includes(';') ? param.dimension.split(';') : param.dimension ) - console.log(dimensions) + // organUnitLevel + params.forEach((param) => { + ;(param.organizationLevel.includes(';') + ? param.organizationLevel.split(';') + : [param.organizationLevel] + ).forEach((org) => { + if (org.includes('LEVEL')) { + const numericLevel = parseInt(org.replace(/LEVEL-/gi, ''), 10) + if (!isNaN(numericLevel)) { + dispatch(updateOrgUnitLevels(numericLevel)) + } + } else if (org.length == 11) { + dispatch(toggleOrgUnitSelection(org)) + } + }) + }) + // date + + // dimension const elementsInfo = allElements.filter((el) => dimensions.includes(el.id)) dispatch( addSelectedElements(elementsInfo.map((el) => ({ id: el.id, displayName: el.displayName }))) @@ -169,15 +221,33 @@ const HistoryPage = ({ dictionaryDb, queryDb }) => { return (
{/* Toolbar Component */} -
+
+
+
+ +
+
+
@@ -198,14 +268,14 @@ const HistoryPage = ({ dictionaryDb, queryDb }) => { {queryHeaders.map((name, index) => ( - + {name} ))} - {downloadQueries.map((el) => ( + {currentPageData.map((el) => ( { {queryHeaders.map((header) => ( - + {editedRow.rowId === el.id && header === 'notes' ? ( { -
-
- - +
) diff --git a/src/renderer/src/pages/MainPage/CategoryCombo.jsx b/src/renderer/src/pages/MainPage/CategoryCombo.jsx index 7add6d6..8b1ff86 100644 --- a/src/renderer/src/pages/MainPage/CategoryCombo.jsx +++ b/src/renderer/src/pages/MainPage/CategoryCombo.jsx @@ -59,8 +59,8 @@ const CategoryDropdownMenu = () => {
{openDropdowns[dropdownId] && ( -
-
    +
    +
      {category?.map((combo) => (
    • { className="cursor-pointer" onChange={(e) => handleSelectCategory(combo.id, e)} /> - {combo.displayName} + {combo.displayName}
    • ))} diff --git a/src/renderer/src/pages/MainPage/OrgUnitLevelMenu.jsx b/src/renderer/src/pages/MainPage/OrgUnitLevelMenu.jsx index ba99f49..1b9e169 100644 --- a/src/renderer/src/pages/MainPage/OrgUnitLevelMenu.jsx +++ b/src/renderer/src/pages/MainPage/OrgUnitLevelMenu.jsx @@ -54,9 +54,9 @@ const OrgUnitLevelMenu = () => {
    {openDropdowns[dropdownId] && ( -
    +
      - {allOrgUnitLevels.map((level) => ( + {allOrgUnitLevels?.map((level) => (
    • { className="cursor-pointer" onChange={(e) => handleOrgUnitLevelChange(level.level, e)} /> - {level.displayName} + {level.displayName}
    • ))} diff --git a/src/renderer/src/pages/MainPage/index.jsx b/src/renderer/src/pages/MainPage/index.jsx index 085db18..089ce0d 100644 --- a/src/renderer/src/pages/MainPage/index.jsx +++ b/src/renderer/src/pages/MainPage/index.jsx @@ -9,6 +9,7 @@ import { generateDownloadingUrl, createDataChunks } from '../../utils/downloadUt import DownloadButton from './DownloadButton' import { useSelector, useDispatch } from 'react-redux' import { triggerLoading, triggerNotification } from '../../reducers/statusReducer' +import Tooltip from '../../components/Tooltip' // eslint-disable-next-line react/prop-types const MainPage = ({ queryDb }) => { @@ -23,7 +24,7 @@ const MainPage = ({ queryDb }) => { let ou = '' if (selectedOrgUnits.length > 0) { let levels = selectedOrgUnitLevels.map((level) => `LEVEL-${level}`).join(';') - ou = `${levels};${selectedOrgUnits.join(';')}&ouMode=SELECTED` + ou = `${levels};${selectedOrgUnits.join(';')}` } else { ou = selectedOrgUnitLevels.map((level) => `LEVEL-${level}`).join(';') } @@ -76,7 +77,8 @@ const MainPage = ({ queryDb }) => { period: pe, dimension: dx, disaggregation: co, - url: downloadingUrl + url: downloadingUrl, + notes: '' }) const csvBlob = new Blob([headerBlob, ...dataChunks], { type: 'text/csv;charset=utf-8' }) const downloadLink = document.createElement('a') @@ -97,47 +99,73 @@ const MainPage = ({ queryDb }) => { } } - const isDownloadDisabled = - new Date(startDate) >= new Date(endDate) || + const isDownloadDisabled = !startDate || !endDate + new Date(startDate) >= new Date(endDate) || addedElements.length == 0 || selectedOrgUnitLevels.length == 0 return (
      -
      -
      -

      Organization Units

      +
      + {/* Organization Units Column */} +
      +

      + Organization Units + +

      -
      -

      Data Elements and Indicators

      + + {/* Data Elements and Indicators Column */} +
      +

      + Data Elements and Indicators +

      -
      -

      Organization Levels

      -
      + + {/* Third Column: Organization Levels, Date Range, Disaggregation */} +
      +

      Organization Levels

      +
      -

      Date Range

      -
      +

      Date Range

      +
      -

      Category Combination

      -
      +

      Disaggregation

      +
      -
      + + {/* Download Button */} +
      +

      Download

      { const dispatch = useDispatch() const openDropdowns = useSelector((state) => state.mouse.openDropdowns) - const timerRef = useRef(null) const handleMouseEnter = (dropdownId) => { @@ -35,7 +34,7 @@ const NavBar = ({ accessToken, username, handleDisconnect }) => { return (
      -