Skip to content

Commit

Permalink
Enable keeping downloading history:
Browse files Browse the repository at this point in the history
a) Add ModalManager and Signout Modal;
b) Create separate modal reducers;
c) Create independent history db;
d) Fix all notification triggering process.
  • Loading branch information
ccxzhang committed Aug 2, 2024
1 parent de09953 commit 7cf2918
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 92 deletions.
21 changes: 8 additions & 13 deletions src/renderer/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,27 @@ import { HashRouter as Router, Routes, Route, Link, Navigate } from 'react-route
import { useSelector, useDispatch } from 'react-redux'
import { initializeAuth, disconnect } from './reducers/authReducer'
import MainPage from './pages/MainPage'
import NotificationModal from './pages/Modal'
import Login from './pages/Login'
import Footer from './pages/Footer'
import About from './pages/About'
import NavBar from './pages/NavBar'
import HistoryPage from './pages/DownloadHistory'
import DataDictionary from './pages/DataDictionary'
import { servicesDb, dictionaryDb } from './service/db'
import ModalManager from './pages/Modals/ModalManager'
import { servicesDb, dictionaryDb, queryDb } from './service/db'
import { openModal } from './reducers/modalReducer'

const App = () => {
const dispatch = useDispatch()
const { dhis2Url, username, password, accessToken } = useSelector((state) => state.auth)
const { isLoading, errorMessage } = useSelector((state) => state.status)
const { isLoading, notification } = useSelector((state) => state.status)

useEffect(() => {
dispatch(initializeAuth())
}, [dispatch])

const handleDisconnect = async () => {
dispatch(disconnect())
dispatch(openModal({ type: 'SIGN_OUT' }))
}

const PrivateRoute = ({ children }) => {
Expand All @@ -49,7 +50,7 @@ const App = () => {
path="/history"
element={
<PrivateRoute>
<HistoryPage dictionaryDb={dictionaryDb} />
<HistoryPage queryDb={queryDb} />
</PrivateRoute>
}
/>
Expand All @@ -61,18 +62,12 @@ const App = () => {
path="/home"
element={
<PrivateRoute>
<MainPage
dhis2Url={dhis2Url}
username={username}
password={password}
dictionaryDb={dictionaryDb}
servicesDb={servicesDb}
/>
<MainPage dictionaryDb={dictionaryDb} servicesDb={servicesDb} queryDb={queryDb} />
</PrivateRoute>
}
/>
</Routes>
{(isLoading || errorMessage) && <NotificationModal />}
<ModalManager />
</div>
<Footer />
</Router>
Expand Down
42 changes: 30 additions & 12 deletions src/renderer/src/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -693,10 +693,6 @@ html {
margin-top: 1rem;
}

.mt-5 {
margin-top: 1.25rem;
}

.mt-8 {
margin-top: 2rem;
}
Expand Down Expand Up @@ -739,10 +735,6 @@ html {
height: 1.5rem;
}

.h-96 {
height: 24rem;
}

.max-h-60 {
max-height: 15rem;
}
Expand Down Expand Up @@ -853,6 +845,12 @@ html {
margin-left: calc(2.5rem * calc(1 - var(--tw-space-x-reverse)));
}

.space-x-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
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));
Expand Down Expand Up @@ -927,10 +925,6 @@ html {
border-right-width: 1px;
}

.border-none {
border-style: none;
}

.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
Expand Down Expand Up @@ -976,6 +970,11 @@ html {
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
}

.bg-gray-300 {
--tw-bg-opacity: 1;
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
}

.bg-gray-500 {
--tw-bg-opacity: 1;
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
Expand All @@ -991,6 +990,11 @@ html {
background-color: rgb(99 102 241 / var(--tw-bg-opacity));
}

.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}

.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
Expand All @@ -1016,6 +1020,10 @@ html {
padding: 1.25rem;
}

.p-6 {
padding: 1.5rem;
}

.px-1 {
padding-left: 0.25rem;
padding-right: 0.25rem;
Expand Down Expand Up @@ -1297,11 +1305,21 @@ html {
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
}

.hover\:bg-gray-400:hover {
--tw-bg-opacity: 1;
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
}

.hover\:bg-indigo-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(79 70 229 / var(--tw-bg-opacity));
}

.hover\:bg-red-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
}

.hover\:text-blue-700:hover {
--tw-text-opacity: 1;
color: rgb(29 78 216 / var(--tw-text-opacity));
Expand Down
11 changes: 1 addition & 10 deletions src/renderer/src/pages/DataDictionary.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,7 @@ const DataDictionary = ({ dictionaryDb }) => {
useLiveQuery(() => dictionaryDbRef.current.catOptionCombos.toArray(), []) || []

const data = useMemo(
() => [
...elements.map((item) => ({
...item,
numerator: null,
denominator: null,
category: 'DataElement'
})),
...indicators.map((item) => ({ ...item, category: 'Indicator' })),
...catOptionCombos.map((item) => ({ ...item, category: 'Category Options' }))
],
() => [...elements, ...indicators, ...catOptionCombos],
[elements, indicators, catOptionCombos]
)

Expand Down
9 changes: 4 additions & 5 deletions src/renderer/src/pages/DownloadHistory/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import { useDispatch, useSelector } from 'react-redux'
import JSZip from 'jszip'
import { useLiveQuery } from 'dexie-react-hooks'
import { objectToCsv, jsonToCsv } from '../../utils/downloadUtils'
import { fetchCsvData, fetchData } from '../../service/useApi'
import { setNotification, setLoading, setError } from '../../reducers/statusReducer'
import { setNotification, setLoading } from '../../reducers/statusReducer'

// eslint-disable-next-line react/prop-types
const HistoryPage = ({ dictionaryDb }) => {
const downloadQueries = useLiveQuery(() => dictionaryDb.query.toArray(), []) || []
const HistoryPage = ({ queryDb }) => {
const downloadQueries = useLiveQuery(() => queryDb.query.toArray(), []) || []
const [selectedRows, setSelectedRows] = useState([])
const dispatch = useDispatch()
const { dhis2Url, username, password } = useSelector((state) => state.auth)
Expand Down Expand Up @@ -49,7 +48,7 @@ const HistoryPage = ({ dictionaryDb }) => {
)
resolve()
} else if (e.data.type === 'error') {
dispatch(setError(e.data.message))
dispatch(setNotification({ message: e.data.message, type: 'error' }))
reject(new Error(e.data.message))
}
}
Expand Down
21 changes: 11 additions & 10 deletions src/renderer/src/pages/MainPage/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useState, useRef } from 'react'
import NotificationModal from '../Modal'
import OrganizationUnitTree from './OrganizationUnitTree'
import OrgUnitLevelMenu from './OrgUnitLevelMenu'
import DateRangeSelector from './DateRangeSelector'
Expand All @@ -10,10 +9,10 @@ import { generatePeriods } from '../../utils/dateUtils'
import { generateDownloadingUrl, createDataChunks } from '../../utils/downloadUtils'
import DownloadButton from './DownloadButton'
import { useSelector, useDispatch } from 'react-redux'
import { setLoading, setError, setNotification } from '../../reducers/statusReducer'
import { triggerLoading, triggerNotification } from '../../reducers/statusReducer'

// eslint-disable-next-line react/prop-types
const MainPage = ({ dictionaryDb, servicesDb }) => {
const MainPage = ({ dictionaryDb, servicesDb, queryDb }) => {
const servicesDbRef = useRef(servicesDb)
const dispatch = useDispatch()
const { selectedOrgUnits, selectedOrgUnitLevels } = useSelector((state) => state.orgUnit)
Expand Down Expand Up @@ -59,22 +58,22 @@ const MainPage = ({ dictionaryDb, servicesDb }) => {
header = headerText.slice(0, indexOfFirstNewline)
}
notificationMessages += `Chunk ${index + 1}: \n${dx}(${firstPe}-${lastPe}) finished\n`
dispatch(setNotification({ message: notificationMessages, type: 'info' }))
dispatch(triggerNotification({ message: notificationMessages, type: 'info' }))
return blob
} catch (error) {
notificationMessages += `Chunk ${index + 1}: \n${dx}(${firstPe}-${lastPe}) failed: ${error.message}\n`
dispatch(setNotification({ message: notificationMessages, type: 'error' }))
dispatch(triggerNotification({ message: notificationMessages, type: 'error' }))
return null
}
}
try {
dispatch(setLoading(true))
dispatch(triggerLoading(true))
const fetchPromises = chunks.map((chunk, index) => fetchChunk(chunk, index))
const results = await Promise.all(fetchPromises)
const dataChunks = results.filter((result) => result !== null)
const headerBlob = new Blob([header + '\n'], { type: 'text/csv' })
if (dataChunks.length > 0) {
await dictionaryDb.query.add({
await queryDb.query.add({
ou: ou,
pe: pe,
dx: dx,
Expand All @@ -86,15 +85,17 @@ const MainPage = ({ dictionaryDb, servicesDb }) => {
downloadLink.href = URL.createObjectURL(csvBlob)
downloadLink.download = 'dhis2_data.csv'
downloadLink.click()
dispatch(setNotification({ message: 'Download completed successfully', type: 'success' }))
dispatch(
triggerNotification({ message: 'Download completed successfully', type: 'success' })
)
} else {
throw new Error('No data chunks were successfully fetched.')
}
} catch (error) {
const errorMessage = error.message ? error.message : error
dispatch(setError(errorMessage))
dispatch(triggerNotification({ message: errorMessage, type: 'error' }))
} finally {
dispatch(setLoading(false))
dispatch(triggerLoading(false))
}
}

Expand Down
36 changes: 36 additions & 0 deletions src/renderer/src/pages/Modals/ModalManager.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import NotificationModal from './NotificationModal'
import SignoutModal from './SignoutModal'

const ModalManager = () => {
const modals = useSelector((state) => state.modal.modals)
const { isLoading, notification } = useSelector((state) => state.status)
const dispatch = useDispatch()

if (modals.length === 0) return null

return (
<>
{modals.map((modal, index) => {
switch (modal.type) {
case 'NOTIFICATION':
return (
<NotificationModal
key={index}
isLoading={isLoading}
message={notification.message}
type={notification.type}
/>
)
case 'SIGN_OUT':
return <SignoutModal key={index} />
default:
return null
}
})}
</>
)
}

export default ModalManager
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { handleExit } from '../reducers/statusReducer'
import { handleExit } from '../../reducers/statusReducer'

const NotificationModal = () => {
const dispatch = useDispatch()
const { isLoading, errorMessage, notification } = useSelector((state) => state.status)
const { isLoading, notification } = useSelector((state) => state.status)

const handleClose = () => dispatch(handleExit())

if (!isLoading && !notification.message && !errorMessage) return null
if (!isLoading && !notification.message) return null

return (
<div className="fixed inset-0 bg-gray-200 bg-opacity-75 flex items-start justify-center pt-20 z-50">
Expand All @@ -21,20 +21,17 @@ const NotificationModal = () => {
)}
{notification.message && (
<p
className={`text-${notification.type === 'success' ? 'green' : 'blue'}-600 mb-2 whitespace-pre-wrap`}
className={`${notification.type === 'error' ? 'text-red-600' : 'text-blue-600'} mb-2 whitespace-pre-wrap`}
>
{notification.message}
</p>
)}
{errorMessage && <p className="text-red-600 mb-2">{errorMessage}</p>}
{!isLoading && (
<button
onClick={handleClose}
className="px-3 py-2 bg-blue-500 text-white rounded hover:bg-blue-700 focus:outline-none"
>
{errorMessage || notification.message ? 'Dismiss' : 'Close'}
</button>
)}
<button
onClick={handleClose}
className="px-3 py-2 bg-blue-500 text-white rounded hover:bg-blue-700 focus:outline-none"
>
{'Close'}
</button>
</div>
</div>
)
Expand Down
Loading

0 comments on commit 7cf2918

Please sign in to comment.