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
+}) => (
+
+
+
+
+
+
+
+
+
+
+ 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 }) => {
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 (
-