From 7f51af3e7af51b61f3afad5c2d2b7dbb1b59e626 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Wed, 4 Dec 2024 00:54:55 +0300 Subject: [PATCH 01/16] fix(congregation): unwanted error message for changing codes --- .../container/useContainer.tsx | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/features/app_notification/container/useContainer.tsx b/src/features/app_notification/container/useContainer.tsx index d123c6c7ca..b631183775 100644 --- a/src/features/app_notification/container/useContainer.tsx +++ b/src/features/app_notification/container/useContainer.tsx @@ -20,11 +20,7 @@ import { dbVisitingSpeakersUpdateRemote, decryptVisitingSpeakers, } from '@services/dexie/visiting_speakers'; -import { - accountTypeState, - congAccessCodeState, - congMasterKeyState, -} from '@states/settings'; +import { accountTypeState } from '@states/settings'; import { decryptData, decryptObject } from '@services/encryption'; import { displaySnackNotification } from '@services/recoil/app'; import { getMessageByCode } from '@services/i18n/translation'; @@ -37,6 +33,7 @@ import usePendingRequests from './usePendingRequests'; import useRemoteNotifications from './useRemoteNotifications'; import useUnverifiedReports from './useUnverifiedReports'; import { apiFetchNotifications } from '@services/api/notification'; +import appDb from '@db/appDb'; const useContainer = () => { const { t } = useAppTranslation(); @@ -61,12 +58,11 @@ const useContainer = () => { const congregationsNotDisapproved = useRecoilValue( congregationsNotDisapprovedState ); - const congMasterKey = useRecoilValue(congMasterKeyState); - const congAccessCode = useRecoilValue(congAccessCodeState); + const accountType = useRecoilValue(accountTypeState); const userID = useRecoilValue(userIDState); - const { data, isPending } = useQuery({ + const { data, isFetching } = useQuery({ enabled: userID.length > 0 && accountType === 'vip' && @@ -160,6 +156,9 @@ const useContainer = () => { ); if (foundCong) { + const settings = await appDb.app_settings.get(1); + const congMasterKey = settings.cong_settings.cong_master_key; + const masterKey = decryptData( data.result.cong_master_key, congMasterKey @@ -184,14 +183,7 @@ const useContainer = () => { severity: 'error', }); } - }, [ - data, - pendingRequests, - setNotifications, - t, - congregationRemotes, - congMasterKey, - ]); + }, [data, pendingRequests, setNotifications, t, congregationRemotes]); const handleRejectedRequests = useCallback(async () => { try { @@ -276,6 +268,9 @@ const useContainer = () => { if (!incoming) return; + const settings = await appDb.app_settings.get(1); + const congAccessCode = settings.cong_settings.cong_access_code; + const remoteAccessCode = data.result.cong_access_code; const accessCode = decryptData(remoteAccessCode, congAccessCode); @@ -295,7 +290,7 @@ const useContainer = () => { severity: 'error', }); } - }, [data, congAccessCode, setApplications]); + }, [data, setApplications]); const handleUnauthorized = useCallback(async () => { const status = data?.status; @@ -311,6 +306,9 @@ const useContainer = () => { if (!incoming) return; + const settings = await appDb.app_settings.get(1); + const congAccessCode = settings.cong_settings.cong_access_code; + const remoteAccessCode = data.result.cong_access_code; const accessCode = decryptData(remoteAccessCode, congAccessCode); @@ -330,10 +328,10 @@ const useContainer = () => { severity: 'error', }); } - }, [data, congAccessCode]); + }, [data]); useEffect(() => { - if (!isPending) { + if (!isFetching) { handleUnauthorized(); handlePendingSpeakersRequests(); @@ -349,7 +347,8 @@ const useContainer = () => { checkUnverifiedReports(); } }, [ - isPending, + data, + isFetching, handleUnauthorized, handlePendingSpeakersRequests, handleRemoteCongregations, From f15447af58301602b028b297bba5af00060b5de4 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Wed, 4 Dec 2024 01:13:15 +0300 Subject: [PATCH 02/16] fix(dashboard): manual backup not running --- src/hooks/useUserAutoLogin.tsx | 53 ++++++++++++++-------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/hooks/useUserAutoLogin.tsx b/src/hooks/useUserAutoLogin.tsx index 1646bd17e9..815d3332c4 100644 --- a/src/hooks/useUserAutoLogin.tsx +++ b/src/hooks/useUserAutoLogin.tsx @@ -16,11 +16,7 @@ import { apiValidateMe } from '@services/api/user'; import { userSignOut } from '@services/firebase/auth'; import { handleDeleteDatabase } from '@services/app'; import { APP_ROLES, isDemo, VIP_ROLES } from '@constants/index'; -import { - accountTypeState, - backupAutoState, - congNumberState, -} from '@states/settings'; +import { accountTypeState, congNumberState } from '@states/settings'; import useFirebaseAuth from '@hooks/useFirebaseAuth'; import logger from '@services/logger/index'; import worker from '@services/worker/backupWorker'; @@ -51,7 +47,6 @@ const useUserAutoLogin = () => { const isAppLoad = useRecoilValue(isAppLoadState); const accountType = useRecoilValue(accountTypeState); const congNumber = useRecoilValue(congNumberState); - const backupAuto = useRecoilValue(backupAutoState); const runFetchVip = useMemo(() => { return ( @@ -195,21 +190,19 @@ const useUserAutoLogin = () => { setCongConnected(true); setIsMFAEnabled(dataVip.result.mfa); - if (backupAuto) { - worker.postMessage({ - field: 'userID', - value: dataVip.result.id, - }); + worker.postMessage({ + field: 'userID', + value: dataVip.result.id, + }); - worker.postMessage({ - field: 'congID', - value: dataVip.result.cong_id, - }); + worker.postMessage({ + field: 'congID', + value: dataVip.result.cong_id, + }); - worker.postMessage({ field: 'accountType', value: 'vip' }); + worker.postMessage({ field: 'accountType', value: 'vip' }); - worker.postMessage('startWorker'); - } + worker.postMessage('startWorker'); } } @@ -230,7 +223,6 @@ const useUserAutoLogin = () => { dataVip, errorVip, congNumber, - backupAuto, setCongConnected, setCongID, setUserID, @@ -287,21 +279,19 @@ const useUserAutoLogin = () => { setCongID(dataPocket.result.app_settings.cong_settings.id); setCongConnected(true); - if (backupAuto) { - worker.postMessage({ - field: 'userID', - value: dataPocket.result.id, - }); + worker.postMessage({ + field: 'userID', + value: dataPocket.result.id, + }); - worker.postMessage({ - field: 'congID', - value: dataPocket.result.app_settings.cong_settings.id, - }); + worker.postMessage({ + field: 'congID', + value: dataPocket.result.app_settings.cong_settings.id, + }); - worker.postMessage({ field: 'accountType', value: 'pocket' }); + worker.postMessage({ field: 'accountType', value: 'pocket' }); - worker.postMessage('startWorker'); - } + worker.postMessage('startWorker'); } setAutoLoginStatus('auto login process completed'); @@ -320,7 +310,6 @@ const useUserAutoLogin = () => { dataPocket, errorPocket, congNumber, - backupAuto, setCongConnected, setCongID, setUserID, From 73bef0ed7e6adf81fd7ab869d58ca78a5cbad622 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:23:32 +0300 Subject: [PATCH 03/16] fix(startup): screens mixed up in some conditions --- .../app_start/shared/hooks/useFeedback.tsx | 4 ++- src/features/app_start/vip/startup/index.tsx | 26 ++++++++++++++----- .../app_start/vip/startup/useStartup.tsx | 2 +- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/features/app_start/shared/hooks/useFeedback.tsx b/src/features/app_start/shared/hooks/useFeedback.tsx index 4a311a465c..a3bc69cde6 100644 --- a/src/features/app_start/shared/hooks/useFeedback.tsx +++ b/src/features/app_start/shared/hooks/useFeedback.tsx @@ -14,7 +14,9 @@ const useFeedback = () => { document.querySelector('#onboarding-error'); const hideMessage = () => { - onboardingError.style.animation = 'fade-out 0.15s forwards'; + if (onboardingError) { + onboardingError.style.animation = 'fade-out 0.15s forwards'; + } }; const showMessage = () => { diff --git a/src/features/app_start/vip/startup/index.tsx b/src/features/app_start/vip/startup/index.tsx index 6522f24135..8d098992cf 100644 --- a/src/features/app_start/vip/startup/index.tsx +++ b/src/features/app_start/vip/startup/index.tsx @@ -27,13 +27,25 @@ const VipStartup = () => { {!isCongCreate && !isEncryptionCodeOpen && isLoading && ( )} - {isUserSignIn && } - {isUserMfaVerify && } - {isUserAccountCreated && } - {isCongCreate && } - {isEmailAuth && } - {isEmailLinkAuth && } - {isEncryptionCodeOpen && } + + {!isLoading && ( + <> + {isUserSignIn && } + + {!isUserSignIn && ( + <> + {isUserMfaVerify && } + {isUserAccountCreated && } + {isCongCreate && } + {isEmailAuth && } + {isEmailLinkAuth && } + {!isCongCreate && isEncryptionCodeOpen && ( + + )} + + )} + + )} ); }; diff --git a/src/features/app_start/vip/startup/useStartup.tsx b/src/features/app_start/vip/startup/useStartup.tsx index 4d068f4081..cb9375ba24 100644 --- a/src/features/app_start/vip/startup/useStartup.tsx +++ b/src/features/app_start/vip/startup/useStartup.tsx @@ -169,7 +169,7 @@ const useStartup = () => { return; } - if (congAccessCode.length === 0) { + if (congAccessCode.length === 0 && congName.length > 0) { setIsStart(false); setIsEncryptionCodeOpen(true); } From d52f2e9589b20843367e3b5fb9c6ca460fe2bd3f Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:35:27 +0300 Subject: [PATCH 04/16] fix(app): persist some app data when logging out --- .../useCongregationAccessCode.tsx | 1 + .../useCongregationDetails.tsx | 1 + .../useCongregationMasterKey.tsx | 1 + src/services/app/index.ts | 14 +++++++++++--- src/services/dexie/app.ts | 2 -- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/features/app_start/vip/congregation_create/congregation_access_code/useCongregationAccessCode.tsx b/src/features/app_start/vip/congregation_create/congregation_access_code/useCongregationAccessCode.tsx index 6dcfa773e3..22c95bca9c 100644 --- a/src/features/app_start/vip/congregation_create/congregation_access_code/useCongregationAccessCode.tsx +++ b/src/features/app_start/vip/congregation_create/congregation_access_code/useCongregationAccessCode.tsx @@ -60,6 +60,7 @@ const useCongregationAccessCode = () => { await dbAppSettingsUpdate({ 'cong_settings.cong_access_code': tmpAccessCode, + 'cong_settings.cong_new': true, }); setIsSetup(false); diff --git a/src/features/app_start/vip/congregation_create/congregation_details/useCongregationDetails.tsx b/src/features/app_start/vip/congregation_create/congregation_details/useCongregationDetails.tsx index aa0f6e2218..142ec6ca18 100644 --- a/src/features/app_start/vip/congregation_create/congregation_details/useCongregationDetails.tsx +++ b/src/features/app_start/vip/congregation_create/congregation_details/useCongregationDetails.tsx @@ -136,6 +136,7 @@ const useCongregationDetails = () => { 'cong_settings.cong_circuit': result.cong_settings.cong_circuit, 'cong_settings.midweek_meeting': midweekMeeting, 'cong_settings.weekend_meeting': weekendMeeting, + 'cong_settings.cong_new': true, }); await setIsNewCongregation(true); diff --git a/src/features/app_start/vip/congregation_create/congregation_master_key/useCongregationMasterKey.tsx b/src/features/app_start/vip/congregation_create/congregation_master_key/useCongregationMasterKey.tsx index e602089a28..e6e80b3296 100644 --- a/src/features/app_start/vip/congregation_create/congregation_master_key/useCongregationMasterKey.tsx +++ b/src/features/app_start/vip/congregation_create/congregation_master_key/useCongregationMasterKey.tsx @@ -58,6 +58,7 @@ const useCongregationMasterKey = () => { await dbAppSettingsUpdate({ 'cong_settings.cong_master_key': tmpMasterKey, + 'cong_settings.cong_new': true, }); setCurrentStep(2); diff --git a/src/services/app/index.ts b/src/services/app/index.ts index 0783721731..f758530402 100644 --- a/src/services/app/index.ts +++ b/src/services/app/index.ts @@ -58,9 +58,17 @@ export const handleDeleteDatabase = async () => { setRootModalOpen(true); await dbAppDelete(); await userSignOut(); - localStorage.removeItem('email'); - localStorage.removeItem('organized_jw_import_next_sync'); - localStorage.removeItem('organized_weekly_schedules'); + + const freezeKeys = ['userConsent', 'organized_whatsnew']; + + const storageKeys = Object.keys(localStorage).filter( + (key) => !freezeKeys.includes(key) + ); + + for (const key of storageKeys) { + localStorage.removeItem(key); + } + window.location.href = './'; }; diff --git a/src/services/dexie/app.ts b/src/services/dexie/app.ts index 8d99dd0fe2..e04c458f31 100644 --- a/src/services/dexie/app.ts +++ b/src/services/dexie/app.ts @@ -4,8 +4,6 @@ import appDb from '@db/appDb'; export const dbAppDelete = async () => { await appDb.close(); await Dexie.delete('organized'); - - localStorage.clear(); }; export const dbAppOpen = async () => { From 4677bc69465cafd2ee41f0f5e9cc09bab3ddcda3 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Wed, 4 Dec 2024 21:20:20 +0300 Subject: [PATCH 05/16] fix(reminders): check if publisher in current report month --- src/features/reminders/useReminders.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/features/reminders/useReminders.tsx b/src/features/reminders/useReminders.tsx index 60a2650c2b..c9b95840a6 100644 --- a/src/features/reminders/useReminders.tsx +++ b/src/features/reminders/useReminders.tsx @@ -4,16 +4,19 @@ import { useAppTranslation } from '@hooks/index'; import { formatDate } from '@services/dateformat'; import { addDays, addMonths, currentReportMonth } from '@utils/date'; import { ReminderItemProps } from './index.types'; +import { branchFieldReportsState } from '@states/branch_field_service_reports'; import useCurrentUser from '@hooks/useCurrentUser'; import useMinistryMonthlyRecord from '@features/ministry/hooks/useMinistryMonthlyRecord'; -import { branchFieldReportsState } from '@states/branch_field_service_reports'; +import usePerson from '@features/persons/hooks/usePerson'; const useReminders = () => { const { t } = useAppTranslation(); + const { personIsPublisher } = usePerson(); + const currentReport = useMemo(() => currentReportMonth(), []); - const { isPublisher, isSecretary } = useCurrentUser(); + const { isSecretary, person } = useCurrentUser(); const { status } = useMinistryMonthlyRecord(currentReport); @@ -22,7 +25,10 @@ const useReminders = () => { const [reminders, setReminders] = useState([]); const checkPubReport = useCallback(() => { + const isPublisher = personIsPublisher(person, currentReport); + if (!isPublisher) return; + if (status !== 'pending') return; const nextMonth = addMonths(`${currentReport}/01`, 1).getMonth(); @@ -45,7 +51,7 @@ const useReminders = () => { return newValues; }); } - }, [isPublisher, status, currentReport, t]); + }, [status, currentReport, t, person, personIsPublisher]); const checkBranchReport = useCallback(() => { if (!isSecretary) return; From 66c6305b6faad00f1ed97f593f3a64987e64680c Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Fri, 6 Dec 2024 23:40:48 +0300 Subject: [PATCH 06/16] fix(ministry): report submission flow --- package-lock.json | 22 +++++++++++++++++++ package.json | 1 + .../withdraw_report/useWithdrawReport.tsx | 1 - .../ministry_report/useMinistryReport.tsx | 2 ++ src/services/app/index.ts | 8 ++++++- 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b084ef9f28..aaa9e72d5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "firebase": "^11.0.2", "i18next": "^24.0.5", "interweave": "^13.1.0", + "jotai": "^2.10.3", "jszip": "^3.10.1", "jw-epub-parser": "^3.29.1", "mui-one-time-password-input": "^3.0.1", @@ -12609,6 +12610,27 @@ "restructure": "^3.0.0" } }, + "node_modules/jotai": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.10.3.tgz", + "integrity": "sha512-Nnf4IwrLhNfuz2JOQLI0V/AgwcpxvVy8Ec8PidIIDeRi4KCFpwTFIpHAAcU+yCgnw/oASYElq9UY0YdUUegsSA==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 4d2df5f87f..9b9c4dc0c9 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "firebase": "^11.0.2", "i18next": "^24.0.5", "interweave": "^13.1.0", + "jotai": "^2.10.3", "jszip": "^3.10.1", "jw-epub-parser": "^3.29.1", "mui-one-time-password-input": "^3.0.1", diff --git a/src/features/ministry/report/publisher_report/monthly_report/withdraw_report/useWithdrawReport.tsx b/src/features/ministry/report/publisher_report/monthly_report/withdraw_report/useWithdrawReport.tsx index c002d2eef8..d58dcbe9de 100644 --- a/src/features/ministry/report/publisher_report/monthly_report/withdraw_report/useWithdrawReport.tsx +++ b/src/features/ministry/report/publisher_report/monthly_report/withdraw_report/useWithdrawReport.tsx @@ -81,7 +81,6 @@ const useWithdrawReport = ({ onClose }: WithdrawReportProps) => { ); const report = structuredClone(findReport); - report.report_data.status = 'pending'; await dbUserFieldServiceReportsSave(report); diff --git a/src/pages/ministry/ministry_report/useMinistryReport.tsx b/src/pages/ministry/ministry_report/useMinistryReport.tsx index d51c739cc5..f70fb61938 100644 --- a/src/pages/ministry/ministry_report/useMinistryReport.tsx +++ b/src/pages/ministry/ministry_report/useMinistryReport.tsx @@ -21,6 +21,8 @@ const useMinistryReport = () => { if (status === 'confirmed') return true; + if (status === 'received') return true; + return !shared_ministry; }, [settings, shared_ministry, status]); diff --git a/src/services/app/index.ts b/src/services/app/index.ts index f758530402..2dc3de689f 100644 --- a/src/services/app/index.ts +++ b/src/services/app/index.ts @@ -59,7 +59,13 @@ export const handleDeleteDatabase = async () => { await dbAppDelete(); await userSignOut(); - const freezeKeys = ['userConsent', 'organized_whatsnew']; + const freezeKeys = [ + 'userConsent', + 'organized_whatsnew', + 'theme', + 'ui_lang', + 'app_font', + ]; const storageKeys = Object.keys(localStorage).filter( (key) => !freezeKeys.includes(key) From de2377b01e327036b1ff5c6119948526b1921de6 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Sat, 7 Dec 2024 07:11:03 +0300 Subject: [PATCH 07/16] chore(reminders): prevent infinite loop --- package-lock.json | 22 ------- package.json | 1 - src/features/reminders/useReminders.tsx | 78 ++++++++++++------------- 3 files changed, 38 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index aaa9e72d5c..b084ef9f28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,6 @@ "firebase": "^11.0.2", "i18next": "^24.0.5", "interweave": "^13.1.0", - "jotai": "^2.10.3", "jszip": "^3.10.1", "jw-epub-parser": "^3.29.1", "mui-one-time-password-input": "^3.0.1", @@ -12610,27 +12609,6 @@ "restructure": "^3.0.0" } }, - "node_modules/jotai": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.10.3.tgz", - "integrity": "sha512-Nnf4IwrLhNfuz2JOQLI0V/AgwcpxvVy8Ec8PidIIDeRi4KCFpwTFIpHAAcU+yCgnw/oASYElq9UY0YdUUegsSA==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=17.0.0", - "react": ">=17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 9b9c4dc0c9..4d2df5f87f 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "firebase": "^11.0.2", "i18next": "^24.0.5", "interweave": "^13.1.0", - "jotai": "^2.10.3", "jszip": "^3.10.1", "jw-epub-parser": "^3.29.1", "mui-one-time-password-input": "^3.0.1", diff --git a/src/features/reminders/useReminders.tsx b/src/features/reminders/useReminders.tsx index c9b95840a6..f24c0cebb0 100644 --- a/src/features/reminders/useReminders.tsx +++ b/src/features/reminders/useReminders.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { useAppTranslation } from '@hooks/index'; import { formatDate } from '@services/dateformat'; @@ -24,37 +24,28 @@ const useReminders = () => { const [reminders, setReminders] = useState([]); - const checkPubReport = useCallback(() => { + const checkPubReport = useMemo(() => { + if (!person) return false; + const isPublisher = personIsPublisher(person, currentReport); - if (!isPublisher) return; + if (!isPublisher) return false; - if (status !== 'pending') return; + if (status !== 'pending') return false; const nextMonth = addMonths(`${currentReport}/01`, 1).getMonth(); const todayMonth = new Date().getMonth(); const todayDate = new Date().getDate(); if (nextMonth === todayMonth && todayDate <= 6) { - setReminders((prev) => { - const newValues = prev.filter( - (record) => record.id !== 'publisher-report' - ); - - newValues.push({ - id: 'publisher-report', - title: t('tr_reminderPublisherReport'), - description: t('tr_reminderPublisherReportDesc'), - path: '/ministry-report', - }); - - return newValues; - }); + return true; } - }, [status, currentReport, t, person, personIsPublisher]); - const checkBranchReport = useCallback(() => { - if (!isSecretary) return; + return false; + }, [currentReport, person, personIsPublisher, status]); + + const checkBranchReport = useMemo(() => { + if (!isSecretary) return false; const branchReport = branchReports.find( (record) => record.report_date === currentReport @@ -62,29 +53,18 @@ const useReminders = () => { const submitted = branchReport?.report_data.submitted ?? false; - if (submitted) return; + if (submitted) return false; const nextMonth = addMonths(`${currentReport}/01`, 1).getMonth(); const todayMonth = new Date().getMonth(); const todayDate = new Date().getDate(); if (nextMonth === todayMonth && todayDate >= 10 && todayDate <= 28) { - setReminders((prev) => { - const newValues = prev.filter( - (record) => record.id !== 'branch-report' - ); - - newValues.push({ - id: 'branch-report', - title: t('tr_reminderBranchReport'), - description: t('tr_reminderBranchReportDesc'), - path: '/reports/branch-office', - }); - - return newValues; - }); + return true; } - }, [isSecretary, branchReports, currentReport, t]); + + return false; + }, [branchReports, currentReport, isSecretary]); const reminderMeTomorrow = () => { const tomorrow = addDays(new Date(), 1); @@ -105,10 +85,28 @@ const useReminders = () => { return; } - checkPubReport(); + const values: ReminderItemProps[] = []; + + if (checkPubReport) { + values.push({ + id: 'publisher-report', + title: t('tr_reminderPublisherReport'), + description: t('tr_reminderPublisherReportDesc'), + path: '/ministry-report', + }); + } + + if (checkBranchReport) { + values.push({ + id: 'branch-report', + title: t('tr_reminderBranchReport'), + description: t('tr_reminderBranchReportDesc'), + path: '/reports/branch-office', + }); + } - checkBranchReport(); - }, [checkPubReport, checkBranchReport]); + setReminders(values); + }, [checkPubReport, checkBranchReport, t]); return { reminders, reminderMeTomorrow }; }; From 52728870ed34c6798638e806947e3678bf3bc233 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Sun, 8 Dec 2024 12:33:51 +0300 Subject: [PATCH 08/16] fix(reports): update submission flow --- .firebaserc | 7 --- src/definition/ministry.ts | 1 - .../submit_report/useSubmitReport.tsx | 11 +++- .../withdraw_report/useWithdrawReport.tsx | 37 ++++++++++- src/pages/ministry/ministry_report/index.tsx | 8 +-- .../ministry_report/useMinistryReport.tsx | 28 +++++++-- .../dexie/cong_field_service_reports.ts | 62 +++++++++++-------- src/services/worker/backupUtils.ts | 4 +- 8 files changed, 108 insertions(+), 50 deletions(-) delete mode 100644 .firebaserc diff --git a/.firebaserc b/.firebaserc deleted file mode 100644 index f06555e1fb..0000000000 --- a/.firebaserc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "projects": { - "production": "cpe-sws", - "default": "cpe-sws" - }, - "targets": {} -} diff --git a/src/definition/ministry.ts b/src/definition/ministry.ts index d91e9d4964..eed84cc6cc 100644 --- a/src/definition/ministry.ts +++ b/src/definition/ministry.ts @@ -24,7 +24,6 @@ export type APRecordType = { }; export type IncomingReport = { - report_id: string; person_uid: string; bible_studies: number; comments: string; diff --git a/src/features/ministry/report/publisher_report/monthly_report/submit_report/useSubmitReport.tsx b/src/features/ministry/report/publisher_report/monthly_report/submit_report/useSubmitReport.tsx index 60164a19a8..2a8f9f0313 100644 --- a/src/features/ministry/report/publisher_report/monthly_report/submit_report/useSubmitReport.tsx +++ b/src/features/ministry/report/publisher_report/monthly_report/submit_report/useSubmitReport.tsx @@ -34,6 +34,7 @@ import { apiPocketValidateMe, } from '@services/api/pocket'; import { decryptData, encryptObject } from '@services/encryption'; +import { CongFieldServiceReportType } from '@definition/cong_field_service_reports'; const useSubmitReport = ({ onClose }: SubmitReportProps) => { const { t } = useAppTranslation(); @@ -146,13 +147,19 @@ const useSubmitReport = ({ onClose }: SubmitReportProps) => { }; const handleSubmitSelf = async () => { - let report = congReports.find( + let report: CongFieldServiceReportType; + + const current = congReports.find( (record) => record.report_data.person_uid === userUID && record.report_data.report_date === selectedMonth ); - if (!report) { + if (current) { + report = structuredClone(current); + } + + if (!current) { report = structuredClone(congFieldServiceReportSchema); report.report_id = crypto.randomUUID(); report.report_data.report_date = selectedMonth; diff --git a/src/features/ministry/report/publisher_report/monthly_report/withdraw_report/useWithdrawReport.tsx b/src/features/ministry/report/publisher_report/monthly_report/withdraw_report/useWithdrawReport.tsx index d58dcbe9de..a94f5b0c8f 100644 --- a/src/features/ministry/report/publisher_report/monthly_report/withdraw_report/useWithdrawReport.tsx +++ b/src/features/ministry/report/publisher_report/monthly_report/withdraw_report/useWithdrawReport.tsx @@ -23,6 +23,9 @@ import { apiPocketFieldServiceReportPost, apiPocketValidateMe, } from '@services/api/pocket'; +import { UserFieldServiceMonthlyReportType } from '@definition/user_field_service_reports'; +import { userFieldServiceMonthlyReportSchema } from '@services/dexie/schema'; +import { congFieldServiceReportsState } from '@states/field_service_reports'; const useWithdrawReport = ({ onClose }: WithdrawReportProps) => { const { t } = useAppTranslation(); @@ -34,6 +37,7 @@ const useWithdrawReport = ({ onClose }: WithdrawReportProps) => { const accountType = useRecoilValue(accountTypeState); const userUID = useRecoilValue(userLocalUIDState); const localAccessCode = useRecoilValue(congAccessCodeState); + const congReports = useRecoilValue(congFieldServiceReportsState); const [isProcessing, setIsProcessing] = useState(false); @@ -80,10 +84,37 @@ const useWithdrawReport = ({ onClose }: WithdrawReportProps) => { (record) => record.report_date === selectedMonth ); - const report = structuredClone(findReport); - report.report_data.status = 'pending'; + let report: UserFieldServiceMonthlyReportType; + + if (!findReport) { + const congReport = congReports.find( + (record) => + record.report_data.person_uid === userUID && + record.report_data.report_date === selectedMonth + ); + + if (congReport) { + report = structuredClone(userFieldServiceMonthlyReportSchema); + report.report_date = selectedMonth; + report.report_data.bible_studies = + congReport.report_data.bible_studies; + report.report_data.comments = congReport.report_data.comments; + report.report_data.hours = congReport.report_data.hours; + report.report_data.shared_ministry = + congReport.report_data.shared_ministry; + report.report_data.status = 'pending'; + report.report_data.updatedAt = congReport.report_data.updatedAt; + } + } + + if (findReport) { + report = structuredClone(findReport); + report.report_data.status = 'pending'; + } - await dbUserFieldServiceReportsSave(report); + if (report) { + await dbUserFieldServiceReportsSave(report); + } await displaySnackNotification({ header: t('tr_done'), diff --git a/src/pages/ministry/ministry_report/index.tsx b/src/pages/ministry/ministry_report/index.tsx index 86cb662121..44218ff91f 100644 --- a/src/pages/ministry/ministry_report/index.tsx +++ b/src/pages/ministry/ministry_report/index.tsx @@ -18,10 +18,10 @@ const MinistryReport = () => { submitOpen, handleCloseSubmit, handleOpenModal, - status, withdrawOpen, handleCloseWithdraw, disabled, + reportStatus, } = useMinistryReport(); return ( @@ -33,10 +33,10 @@ const MinistryReport = () => { variant="main" onClick={handleOpenModal} disabled={disabled} - startIcon={status === 'pending' ? : } - color={status !== 'pending' && 'orange'} + startIcon={reportStatus === 'pending' ? : } + color={reportStatus !== 'pending' && 'orange'} > - {status === 'pending' + {reportStatus === 'pending' ? t('tr_btnSubmitReport') : t('tr_undoSubmission')} diff --git a/src/pages/ministry/ministry_report/useMinistryReport.tsx b/src/pages/ministry/ministry_report/useMinistryReport.tsx index f70fb61938..b7836e866f 100644 --- a/src/pages/ministry/ministry_report/useMinistryReport.tsx +++ b/src/pages/ministry/ministry_report/useMinistryReport.tsx @@ -1,6 +1,9 @@ import { useMemo, useState } from 'react'; import { useRecoilValue } from 'recoil'; -import { reportUserSelectedMonthState } from '@states/user_field_service_reports'; +import { + reportUserSelectedMonthState, + userFieldServiceMonthlyReportsState, +} from '@states/user_field_service_reports'; import { settingsState } from '@states/settings'; import useMinistryMonthlyRecord from '@features/ministry/hooks/useMinistryMonthlyRecord'; @@ -10,10 +13,25 @@ const useMinistryReport = () => { const { status, shared_ministry } = useMinistryMonthlyRecord(selectedMonth); const settings = useRecoilValue(settingsState); + const monthlyReports = useRecoilValue(userFieldServiceMonthlyReportsState); const [submitOpen, setSubmitOpen] = useState(false); const [withdrawOpen, setWithdrawOpen] = useState(false); + const reportStatus = useMemo(() => { + const report = monthlyReports.find( + (record) => record.report_date === selectedMonth + ); + + if (!report) return status; + + const userStatus = report.report_data.status; + + if (userStatus === 'pending') return 'pending'; + + return status; + }, [status, monthlyReports, selectedMonth]); + const disabled = useMemo(() => { if (!settings.cong_settings.data_sync.value) { return true; @@ -21,8 +39,6 @@ const useMinistryReport = () => { if (status === 'confirmed') return true; - if (status === 'received') return true; - return !shared_ministry; }, [settings, shared_ministry, status]); @@ -31,11 +47,11 @@ const useMinistryReport = () => { const handleCloseWithdraw = () => setWithdrawOpen(false); const handleOpenModal = () => { - if (status === 'pending') { + if (reportStatus === 'pending') { setSubmitOpen(true); } - if (status === 'submitted') { + if (reportStatus === 'submitted' || reportStatus === 'received') { setWithdrawOpen(true); } }; @@ -44,10 +60,10 @@ const useMinistryReport = () => { submitOpen, handleOpenModal, handleCloseSubmit, - status, handleCloseWithdraw, withdrawOpen, disabled, + reportStatus, }; }; diff --git a/src/services/dexie/cong_field_service_reports.ts b/src/services/dexie/cong_field_service_reports.ts index 0914965541..5dc50c64ce 100644 --- a/src/services/dexie/cong_field_service_reports.ts +++ b/src/services/dexie/cong_field_service_reports.ts @@ -17,6 +17,7 @@ export const dbFieldServiceReportsBulkSave = async ( export const dbHandleIncomingReports = async (reports: IncomingReport[]) => { const congReportsAll = await appDb.cong_field_service_reports.toArray(); + const congReports = congReportsAll.filter( (record) => !record.report_data._deleted ); @@ -32,7 +33,9 @@ export const dbHandleIncomingReports = async (reports: IncomingReport[]) => { ); const findReport = congReports.find( - (r) => r.report_id === record.report_id + (r) => + r.report_data.report_date === record.report_month && + r.report_data.person_uid === record.person_uid ); let allowAdd = false; @@ -62,42 +65,49 @@ export const dbHandleIncomingReports = async (reports: IncomingReport[]) => { if (!allowAdd) continue; // remove deleted report on current - if (record._deleted) { + if (record._deleted && findReport) { const report = structuredClone(findReport); report.report_data._deleted = true; report.report_data.updatedAt = record.updatedAt; await dbFieldServiceReportsSave(report); - - continue; } // add new report - let report: CongFieldServiceReportType; + if (!record._deleted) { + const pubReport = congReportsAll.find( + (r) => + r.report_data.report_date === record.report_month && + r.report_data.person_uid === record.person_uid + ); - if (!findReport) { - report = structuredClone(congFieldServiceReportSchema); - report.report_id = record.report_id; - report.report_data.person_uid = record.person_uid; - } + let report: CongFieldServiceReportType; - if (findReport) { - report = structuredClone(findReport); - } + if (!pubReport) { + report = structuredClone(congFieldServiceReportSchema); + report.report_id = crypto.randomUUID(); + report.report_data.person_uid = record.person_uid; + } - report.report_data.updatedAt = record.updatedAt; - report.report_data.bible_studies = record.bible_studies; - report.report_data.comments = record.comments; - report.report_data.hours.field_service = record.hours; - report.report_data.hours.credit = { - approved: 0, - value: record.hours_credits, - }; - report.report_data.report_date = record.report_month; - report.report_data.shared_ministry = record.shared_ministry; - report.report_data.status = 'received'; - - await dbFieldServiceReportsSave(report); + if (pubReport) { + report = structuredClone(pubReport); + } + + report.report_data.updatedAt = record.updatedAt; + report.report_data.bible_studies = record.bible_studies; + report.report_data.comments = record.comments; + report.report_data.hours.field_service = record.hours; + report.report_data.hours.credit = { + approved: 0, + value: record.hours_credits, + }; + report.report_data.report_date = record.report_month; + report.report_data.shared_ministry = record.shared_ministry; + report.report_data.status = 'received'; + report.report_data._deleted = false; + + await dbFieldServiceReportsSave(report); + } } }; diff --git a/src/services/worker/backupUtils.ts b/src/services/worker/backupUtils.ts index 785ad6d2ad..f6f6d00122 100644 --- a/src/services/worker/backupUtils.ts +++ b/src/services/worker/backupUtils.ts @@ -1238,7 +1238,9 @@ export const dbExportDataBackup = async (backupData: BackupDataType) => { for (const report of remoteReports) { const findReport = cong_field_service_reports.find( - (record) => record.report_id === report.report_id + (record) => + record.report_data.person_uid === report.person_uid && + record.report_data.report_date === report.report_month ); if (!findReport) { From a88d0d88e99244cf1c03f83dec7db44b0ab641b4 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:13:48 +0300 Subject: [PATCH 09/16] fix(reports): remove duplicate reports --- src/services/app/index.ts | 2 + .../dexie/cong_field_service_reports.ts | 77 +++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/src/services/app/index.ts b/src/services/app/index.ts index 2dc3de689f..4bdf5ab1f7 100644 --- a/src/services/app/index.ts +++ b/src/services/app/index.ts @@ -20,6 +20,7 @@ import { setSongs } from '@services/recoil/songs'; import { schedulesBuildHistoryList } from './schedules'; import { setAssignmentsHistory } from '@services/recoil/schedules'; import { dbSchedAuxClassUpdate } from '@services/dexie/schedules'; +import { dbRemoveDuplicateReports } from '@services/dexie/cong_field_service_reports'; export const loadApp = async () => { const appLang = await promiseGetRecoil(appLangState); @@ -42,6 +43,7 @@ export const runUpdater = async () => { await dbWeekTypeUpdate(); await dbAssignmentUpdate(); await dbSchedAuxClassUpdate(); + await dbRemoveDuplicateReports(); }; export const userLogoutSuccess = async () => { diff --git a/src/services/dexie/cong_field_service_reports.ts b/src/services/dexie/cong_field_service_reports.ts index 5dc50c64ce..e2616ef73b 100644 --- a/src/services/dexie/cong_field_service_reports.ts +++ b/src/services/dexie/cong_field_service_reports.ts @@ -111,3 +111,80 @@ export const dbHandleIncomingReports = async (reports: IncomingReport[]) => { } } }; + +export const dbRemoveDuplicateReports = async () => { + try { + const congReportsAll = await appDb.cong_field_service_reports.toArray(); + + const congReports = congReportsAll.filter( + (record) => !record.report_data._deleted + ); + + type recType = { + person_uid: string; + months: { + report_date: string; + reports: CongFieldServiceReportType[]; + }[]; + }[]; + + const personReportsByMonth = congReports.reduce((acc: recType, record) => { + const personRecord = acc.find( + (p) => p.person_uid === record.report_data.person_uid + ); + + if (!personRecord) { + acc.push({ + person_uid: record.report_data.person_uid, + months: [ + { + report_date: record.report_data.report_date, + reports: [record], + }, + ], + }); + } + + if (personRecord) { + const monthReport = personRecord.months.find( + (r) => r.report_date === record.report_data.report_date + ); + + if (!monthReport) { + personRecord.months.push({ + report_date: record.report_data.report_date, + reports: [record], + }); + } + + if (monthReport) { + monthReport.reports.push(record); + } + } + + return acc; + }, []); + + const duplicateReports = personReportsByMonth.filter((record) => + record.months.find((month) => month.reports.length > 1) + ); + + for await (const person of duplicateReports) { + for await (const month of person.months) { + const leastReport = month.reports + .sort((a, b) => + a.report_data.updatedAt.localeCompare(b.report_data.updatedAt) + ) + .at(0); + + const report = structuredClone(leastReport); + report.report_data._deleted = true; + report.report_data.updatedAt = new Date().toISOString(); + + await dbFieldServiceReportsSave(report); + } + } + } catch (error) { + console.error(error); + } +}; From 9224e362a7ccb5fc1de08c56cdbc9f62142c06ae Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:41:38 +0300 Subject: [PATCH 10/16] fix(reports): update year range when exporting congregation cards --- src/features/reports/hooks/useCongregationCard.tsx | 10 +++++----- src/views/reports/S21/shared/TableHeader.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/features/reports/hooks/useCongregationCard.tsx b/src/features/reports/hooks/useCongregationCard.tsx index 6d6f4e609d..ae7d71b778 100644 --- a/src/features/reports/hooks/useCongregationCard.tsx +++ b/src/features/reports/hooks/useCongregationCard.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; import { useAppTranslation } from '@hooks/index'; import { S21CardData, S21CardMonthData } from '@definition/report'; -import { createArrayFromMonths, currentReportMonth } from '@utils/date'; +import { createArrayFromMonths, currentServiceYear } from '@utils/date'; import { JWLangState, monthNamesState } from '@states/app'; import useReportMonthly from './useReportMonthly'; @@ -17,11 +17,11 @@ const useCongregationCard = () => { const years = useMemo(() => { const result: string[] = []; - const currentMonth = currentReportMonth(); - const year = currentMonth.split('/')[0]; - const prevYear = String(+year - 1).toString(); - result.push(prevYear, year); + const currentYear = currentServiceYear(); + const prevYear = String(+currentYear - 1).toString(); + + result.push(prevYear, currentYear); return result; }, []); diff --git a/src/views/reports/S21/shared/TableHeader.tsx b/src/views/reports/S21/shared/TableHeader.tsx index b79cd5d8b2..b694e165bf 100644 --- a/src/views/reports/S21/shared/TableHeader.tsx +++ b/src/views/reports/S21/shared/TableHeader.tsx @@ -11,7 +11,7 @@ const TableHeader = ({ data }: S21Type) => { - {t('tr_serviceYear')} + {t('tr_serviceYear').replaceAll('-', '-\u000A')} {data.year} From 99fccd1e03920cf34093293bad9e4fa55ddfa28d Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:43:35 +0300 Subject: [PATCH 11/16] fix(locales): update language path for hungarian --- src/constants/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants/index.ts b/src/constants/index.ts index be23487d94..5607abd640 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -65,7 +65,7 @@ export const LANGUAGE_LIST = [ { code: 'cv', locale: 'ceb-PH', name: 'Cebuano' }, { code: 'kha', locale: 'mn-MN', name: 'монгол' }, { code: 'rea', locale: 'hy-AM', name: 'Հայերեն' }, - { code: 'h', locale: 'hy-HU', name: 'magyar' }, + { code: 'h', locale: 'hu-HU', name: 'magyar' }, { code: 'tpo', locale: 'pt-PT', name: 'Português (Portugal)' }, { code: 'fi', locale: 'fi-FI', name: 'suomi' }, { code: 'st', locale: 'et-EE', name: 'eesti' }, From 409465e920bf103dc5dab6e036d22c5db4a47144 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Sun, 8 Dec 2024 14:24:09 +0300 Subject: [PATCH 12/16] fix(locales): issue with tagalog language translation --- src/RootWrap.tsx | 15 +------- src/constants/index.ts | 2 +- .../language_switcher/useLanguage.tsx | 27 +++----------- src/features/whats_new/useWhatsNew.tsx | 13 +++++-- src/hooks/useAppTranslation.tsx | 9 ++++- src/services/i18n/index.ts | 36 +++++++++---------- src/services/i18n/public_talks.ts | 7 +++- src/services/i18n/songs.ts | 7 +++- src/services/i18n/translation.ts | 9 +++++ src/states/app.ts | 15 +------- 10 files changed, 63 insertions(+), 77 deletions(-) diff --git a/src/RootWrap.tsx b/src/RootWrap.tsx index be4331c299..44a299e490 100644 --- a/src/RootWrap.tsx +++ b/src/RootWrap.tsx @@ -21,20 +21,7 @@ import WaitingLoader from '@components/waiting_loader'; const Migration = lazy(() => import('./migration')); -const getFont = () => { - const cookiesConsent = Boolean(localStorage.getItem('userConsent')); - - if (cookiesConsent) { - return localStorage.getItem('app_font') || 'Inter'; - } - - const hash = new URL(window.location.href).hash; - const params = new URLSearchParams(hash.substring(2)); - - return params.get('font') || 'Inter'; -}; - -const font = getFont(); +const font = localStorage.getItem('app_font') || 'Inter'; const cache = createCache({ key: 'css', diff --git a/src/constants/index.ts b/src/constants/index.ts index 5607abd640..aa5bfac446 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -19,7 +19,7 @@ export const LANGUAGE_LIST = [ { code: 't', locale: 'pt-BR', name: 'Português (Brasil)' }, { code: 'u', locale: 'ru-RU', name: 'русский' }, { code: 'm', locale: 'ro-RO', name: 'Română' }, - { code: 'tg', locale: 'tl-PH', name: 'Tagalog' }, + { code: 'tg', locale: 'tl-PH', name: 'Tagalog', identifier: 'tgl' }, { code: 'tnd', locale: 'mg-TND', diff --git a/src/features/language_switcher/useLanguage.tsx b/src/features/language_switcher/useLanguage.tsx index d33e6a07bb..287dfdae62 100644 --- a/src/features/language_switcher/useLanguage.tsx +++ b/src/features/language_switcher/useLanguage.tsx @@ -1,28 +1,19 @@ import { useEffect, useState } from 'react'; -import { useSearchParams } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; import { useBreakpoints } from '@hooks/index'; -import { cookiesConsentState, isAppLoadState } from '@states/app'; +import { isAppLoadState } from '@states/app'; import { LANGUAGE_LIST } from '@constants/index'; import { getTranslation } from '@services/i18n/translation'; import { FullnameOption } from '@definition/settings'; import { dbAppSettingsUpdate } from '@services/dexie/settings'; -import { - accountTypeState, - settingsState, - userDataViewState, -} from '@states/settings'; +import { settingsState, userDataViewState } from '@states/settings'; const useLanguage = () => { const { tabletDown } = useBreakpoints(); - const [, setParams] = useSearchParams(); - const isAppLoad = useRecoilValue(isAppLoadState); const dataView = useRecoilValue(userDataViewState); const settings = useRecoilValue(settingsState); - const cookiesConsent = useRecoilValue(cookiesConsentState); - const accountType = useRecoilValue(accountTypeState); const [anchorEl, setAnchorEl] = useState(null); const isMenuOpen = Boolean(anchorEl); @@ -45,18 +36,8 @@ const useLanguage = () => { const font = LANGUAGE_LIST.find((lang) => lang.locale === ui_lang)?.font || 'Inter'; - if (cookiesConsent || accountType === 'pocket') { - localStorage.setItem('ui_lang', ui_lang); - localStorage.setItem('app_font', font); - } - - if (!cookiesConsent && accountType !== 'pocket') { - setParams((params) => { - params.set('locale', ui_lang); - params.set('font', font); - return params; - }); - } + localStorage.setItem('ui_lang', ui_lang); + localStorage.setItem('app_font', font); handleClose(); window.location.reload(); diff --git a/src/features/whats_new/useWhatsNew.tsx b/src/features/whats_new/useWhatsNew.tsx index 228b504236..6d4c39df0a 100644 --- a/src/features/whats_new/useWhatsNew.tsx +++ b/src/features/whats_new/useWhatsNew.tsx @@ -3,7 +3,7 @@ import { SwiperRef } from 'swiper/react'; import { useRecoilValue } from 'recoil'; import { useAppTranslation } from '@hooks/index'; import { ReleaseNoteType, UpdateStatusType } from '@definition/app'; -import { isDemo } from '@constants/index'; +import { isDemo, LANGUAGE_LIST } from '@constants/index'; import { ImageSlide } from './index.types'; import { appLangState } from '@states/app'; @@ -22,9 +22,16 @@ const useWhatsNew = () => { const [improvements, setImprovements] = useState([]); const [currentImage, setCurrentImage] = useState(0); + const identifier = useMemo(() => { + return ( + LANGUAGE_LIST.find((record) => record.locale === appLang)?.identifier || + appLang + ); + }, [appLang]); + const releases = useMemo(() => { - return i18n.options.resources[appLang].releases as ReleaseNoteType; - }, [appLang, i18n]); + return i18n.options.resources[identifier].releases as ReleaseNoteType; + }, [identifier, i18n]); const version = useMemo(() => { const releasesDates = Object.keys(releases); diff --git a/src/hooks/useAppTranslation.tsx b/src/hooks/useAppTranslation.tsx index 69d813849f..9ea74db51b 100644 --- a/src/hooks/useAppTranslation.tsx +++ b/src/hooks/useAppTranslation.tsx @@ -1,7 +1,14 @@ +import { LANGUAGE_LIST } from '@constants/index'; import { useTranslation } from 'react-i18next'; const useHookTranslation = () => { - const { t, i18n } = useTranslation(); + const appLang = localStorage.getItem('ui_lang'); + + const identifier = + LANGUAGE_LIST.find((record) => record.locale === appLang)?.identifier || + appLang; + + const { t, i18n } = useTranslation('ui', { lng: identifier }); return { t, i18n }; }; diff --git a/src/services/i18n/index.ts b/src/services/i18n/index.ts index 99b5b89a87..07f14691c2 100644 --- a/src/services/i18n/index.ts +++ b/src/services/i18n/index.ts @@ -1,37 +1,34 @@ -import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; +import i18n from 'i18next'; + +import { LANGUAGE_LIST } from '@constants/index'; export const defaultNS = 'ui'; const resources = {}; -const getAppLang = () => { - const langStorage = localStorage.getItem('ui_lang'); - - if (langStorage) { - return langStorage; - } +const appLang = localStorage.getItem('ui_lang'); - const hash = new URL(window.location.href).hash; - const params = new URLSearchParams(hash.substring(2)); +const identifier = + LANGUAGE_LIST.find((record) => record.locale === appLang)?.identifier || + appLang; - return params.get('locale')?.toString() || 'en'; -}; +const languages = [{ locale: appLang, identifier }]; -const appLang = getAppLang(); - -const languages = [appLang]; - -if (!languages.includes('en')) languages.push('en'); +if (!languages.some((r) => r.locale === 'en')) + languages.push({ locale: 'en', identifier: 'en' }); // programatically load all locales -for await (const language of languages) { +for await (const record of languages) { + const language = record.locale; + const activities = await import(`@locales/${language}/activities.json`).then( (module) => module.default ); const congregation = await import( `@locales/${language}/congregation.json` ).then((module) => module.default); + const dashboard = await import(`@locales/${language}/dashboard.json`).then( (module) => module.default ); @@ -72,7 +69,7 @@ for await (const language of languages) { (module) => module.default ); - resources[language] = { + resources[record.identifier] = { ui: { ...activities, ...congregation, @@ -94,8 +91,9 @@ for await (const language of languages) { i18n.use(initReactI18next).init({ resources, defaultNS, - lng: appLang, + lng: identifier, fallbackLng: 'en', + supportedLngs: ['en', identifier], interpolation: { escapeValue: false }, }); diff --git a/src/services/i18n/public_talks.ts b/src/services/i18n/public_talks.ts index 3ac359ad91..d682a35b50 100644 --- a/src/services/i18n/public_talks.ts +++ b/src/services/i18n/public_talks.ts @@ -1,8 +1,13 @@ import { getI18n } from 'react-i18next'; import { PublicTalkType } from '@definition/public_talks'; +import { LANGUAGE_LIST } from '@constants/index'; export const publicTalksBuildList = (language: string) => { - const translations = getI18n().options.resources[language].talks; + const identifier = + LANGUAGE_LIST.find((record) => record.locale === language)?.identifier || + language; + + const translations = getI18n().options.resources[identifier].talks; const result: PublicTalkType[] = []; for (const [key, value] of Object.entries(translations)) { diff --git a/src/services/i18n/songs.ts b/src/services/i18n/songs.ts index 72231b239a..b0b5ea4bf4 100644 --- a/src/services/i18n/songs.ts +++ b/src/services/i18n/songs.ts @@ -1,8 +1,13 @@ import { getI18n } from 'react-i18next'; import { SongType } from '@definition/songs'; +import { LANGUAGE_LIST } from '@constants/index'; export const songsBuildList = (language: string) => { - const translations = getI18n().options.resources[language].songs; + const identifier = + LANGUAGE_LIST.find((record) => record.locale === language)?.identifier || + language; + + const translations = getI18n().options.resources[identifier].songs; const result: SongType[] = []; for (const [key, value] of Object.entries(translations)) { diff --git a/src/services/i18n/translation.ts b/src/services/i18n/translation.ts index ac82459398..676813a301 100644 --- a/src/services/i18n/translation.ts +++ b/src/services/i18n/translation.ts @@ -1,3 +1,4 @@ +import { LANGUAGE_LIST } from '@constants/index'; import { getI18n } from 'react-i18next'; export const getTranslation = ({ @@ -14,6 +15,14 @@ export const getTranslation = ({ if (i18n) { if (!language) language = i18n.language; + if (language) { + const identifier = + LANGUAGE_LIST.find((record) => record.locale === language) + ?.identifier || language; + + language = identifier; + } + return i18n.t(key, { lng: language, ...params }); } }; diff --git a/src/states/app.ts b/src/states/app.ts index d8d3de7702..ea6701564c 100644 --- a/src/states/app.ts +++ b/src/states/app.ts @@ -9,19 +9,6 @@ import { ReactElement } from 'react'; import { LANGUAGE_LIST } from '@constants/index'; import { CongregationUserType } from '@definition/api'; -const getAppLang = () => { - const langStorage = localStorage.getItem('ui_lang'); - - if (langStorage) { - return langStorage; - } - - const hash = new URL(window.location.href).hash; - const params = new URLSearchParams(hash.substring(2)); - - return params.get('locale')?.toString() || 'en'; -}; - export const isDarkThemeState = atom({ key: 'isDarkTheme', default: localStorageGetItem('theme') === 'dark' ? true : false, @@ -64,7 +51,7 @@ export const isLoginOpenState = atom({ export const appLangState = atom({ key: 'appLang', - default: getAppLang(), + default: localStorage?.getItem('ui_lang') || 'en', }); export const monthNamesState = selector({ From bd153fcb2e8e58d81113ae1ad724ce03378055e2 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Sun, 8 Dec 2024 14:44:47 +0300 Subject: [PATCH 13/16] fix(templates): use 24 hour format settings in exported pdf --- .../midweek_meeting/useMidweekMeeting.tsx | 15 +++++++++++--- .../weekend_meeting/useWeekendMeeting.tsx | 20 ++++++++++++++++--- src/services/app/schedules.ts | 11 +++++++++- src/services/i18n/index.ts | 2 +- 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/features/meetings/weekly_schedules/midweek_meeting/useMidweekMeeting.tsx b/src/features/meetings/weekly_schedules/midweek_meeting/useMidweekMeeting.tsx index 54cd47b3fc..590034a126 100644 --- a/src/features/meetings/weekly_schedules/midweek_meeting/useMidweekMeeting.tsx +++ b/src/features/meetings/weekly_schedules/midweek_meeting/useMidweekMeeting.tsx @@ -2,9 +2,10 @@ import { useMemo, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { useAppTranslation, useIntersectionObserver } from '@hooks/index'; import { schedulesState } from '@states/schedules'; -import { addMonths, getWeekDate } from '@utils/date'; +import { addMonths, generateDateFromTime, getWeekDate } from '@utils/date'; import { formatDate } from '@services/dateformat'; import { + hour24FormatState, midweekMeetingClassCountState, midweekMeetingOpeningPrayerAutoAssign, midweekMeetingTimeState, @@ -38,6 +39,7 @@ const useMidweekMeeting = () => { const userUID = useRecoilValue(userLocalUIDState); const pgmStart = useRecoilValue(midweekMeetingTimeState); const lang = useRecoilValue(JWLangState); + const use24 = useRecoilValue(hour24FormatState); const openingPrayerAuto = useRecoilValue( midweekMeetingOpeningPrayerAutoAssign ); @@ -168,16 +170,23 @@ const useMidweekMeeting = () => { const partTimings = useMemo(() => { if (!schedule && !source) return; + let meetingStart = pgmStart; + + if (!use24) { + const date = generateDateFromTime(pgmStart); + meetingStart = formatDate(date, 'h:mm'); + } + const result = schedulesMidweekGetTiming({ schedule, dataView, - pgmStart, + pgmStart: meetingStart, source, lang, }); return result; - }, [schedule, source, dataView, pgmStart, lang]); + }, [schedule, source, dataView, pgmStart, lang, use24]); const handleGoCurrent = () => { const now = getWeekDate(); diff --git a/src/features/meetings/weekly_schedules/weekend_meeting/useWeekendMeeting.tsx b/src/features/meetings/weekly_schedules/weekend_meeting/useWeekendMeeting.tsx index ea984da0dd..119e2cced9 100644 --- a/src/features/meetings/weekly_schedules/weekend_meeting/useWeekendMeeting.tsx +++ b/src/features/meetings/weekly_schedules/weekend_meeting/useWeekendMeeting.tsx @@ -2,9 +2,15 @@ import { useMemo, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { useAppTranslation, useIntersectionObserver } from '@hooks/index'; import { schedulesState } from '@states/schedules'; -import { addMonths, getWeekDate, timeAddMinutes } from '@utils/date'; +import { + addMonths, + generateDateFromTime, + getWeekDate, + timeAddMinutes, +} from '@utils/date'; import { formatDate } from '@services/dateformat'; import { + hour24FormatState, userDataViewState, userLocalUIDState, weekendMeetingOpeningPrayerAutoAssignState, @@ -39,6 +45,7 @@ const useWeekendMeeting = () => { const sources = useRecoilValue(sourcesState); const userUID = useRecoilValue(userLocalUIDState); const pgmStart = useRecoilValue(weekendMeetingTimeState); + const use24 = useRecoilValue(hour24FormatState); const openingPrayerAuto = useRecoilValue( weekendMeetingOpeningPrayerAutoAssignState ); @@ -179,7 +186,14 @@ const useWeekendMeeting = () => { const partTimings = useMemo(() => { const timings = {} as WeekendMeetingTimingsType; - timings.pgm_start = timeAddMinutes(pgmStart, 0); + let meetingStart = pgmStart; + + if (!use24) { + const date = generateDateFromTime(pgmStart); + meetingStart = formatDate(date, 'h:mm'); + } + + timings.pgm_start = timeAddMinutes(meetingStart, 0); timings.public_talk = timeAddMinutes(timings.pgm_start, 5); timings.middle_song = timeAddMinutes(timings.public_talk, 30); timings.w_study = timeAddMinutes(timings.middle_song, 5); @@ -194,7 +208,7 @@ const useWeekendMeeting = () => { } return timings; - }, [pgmStart, weekType]); + }, [pgmStart, weekType, use24]); const handleGoCurrent = () => { const now = getWeekDate(); diff --git a/src/services/app/schedules.ts b/src/services/app/schedules.ts index c7ee379c62..2ee5a789a6 100644 --- a/src/services/app/schedules.ts +++ b/src/services/app/schedules.ts @@ -19,6 +19,7 @@ import { weekendMeetingOpeningPrayerAutoAssignState, weekendMeetingWeekdayState, weekendMeetingWTStudyConductorDefaultState, + hour24FormatState, } from '@states/settings'; import { sourcesState } from '@states/sources'; import { assignmentsHistoryState, schedulesState } from '@states/schedules'; @@ -64,6 +65,7 @@ import { addMonths, addWeeks, dateFormatFriendly, + generateDateFromTime, timeAddMinutes, } from '@utils/date'; import { applyAssignmentFilters, personIsElder } from './persons'; @@ -2052,7 +2054,14 @@ export const schedulesMidweekData = async ( const result = {} as MidweekMeetingDataType; // get meeting parts timing - const pgmStart = await promiseGetRecoil(midweekMeetingTimeState); + let pgmStart: string = await promiseGetRecoil(midweekMeetingTimeState); + const use24: boolean = await promiseGetRecoil(hour24FormatState); + + if (!use24) { + const date = generateDateFromTime(pgmStart); + pgmStart = formatDate(date, 'h:mm'); + } + result.timing = schedulesMidweekGetTiming({ schedule, source, diff --git a/src/services/i18n/index.ts b/src/services/i18n/index.ts index 07f14691c2..5cd6284d99 100644 --- a/src/services/i18n/index.ts +++ b/src/services/i18n/index.ts @@ -7,7 +7,7 @@ export const defaultNS = 'ui'; const resources = {}; -const appLang = localStorage.getItem('ui_lang'); +const appLang = localStorage.getItem('ui_lang') || 'en'; const identifier = LANGUAGE_LIST.find((record) => record.locale === appLang)?.identifier || From 5ef6cd55d3e0a686aab8e2e37fccb2269f240d88 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Sun, 8 Dec 2024 14:58:26 +0300 Subject: [PATCH 14/16] fix(persons): update date of baptism change flow --- .../baptized_publisher/useBaptizedPublisher.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/features/persons/spiritual_status/baptized_publisher/useBaptizedPublisher.tsx b/src/features/persons/spiritual_status/baptized_publisher/useBaptizedPublisher.tsx index ac26956af1..09d19e646e 100644 --- a/src/features/persons/spiritual_status/baptized_publisher/useBaptizedPublisher.tsx +++ b/src/features/persons/spiritual_status/baptized_publisher/useBaptizedPublisher.tsx @@ -184,6 +184,7 @@ const useBaptizedPublisher = () => { newPerson.person_data.publisher_baptized.baptism_date.value = value === null ? null : new Date(value).toISOString(); + newPerson.person_data.publisher_baptized.baptism_date.updatedAt = new Date().toISOString(); @@ -191,7 +192,9 @@ const useBaptizedPublisher = () => { (record) => !record._deleted && record.start_date !== null ); - if (histories.length === 0 && value) { + const firstReport = person.person_data.first_report?.value || ''; + + if (histories.length === 0 && value && firstReport.length === 0) { const startMonth = dateFirstDayMonth(value).toISOString(); newPerson.person_data.publisher_baptized.history.push({ From 4ba5a972eaa123bec642ff1f582fc9f7044fe192 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Sun, 8 Dec 2024 15:10:29 +0300 Subject: [PATCH 15/16] chore(hooks): add default fallback language --- src/hooks/useAppTranslation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useAppTranslation.tsx b/src/hooks/useAppTranslation.tsx index 9ea74db51b..617c3b6dfc 100644 --- a/src/hooks/useAppTranslation.tsx +++ b/src/hooks/useAppTranslation.tsx @@ -2,7 +2,7 @@ import { LANGUAGE_LIST } from '@constants/index'; import { useTranslation } from 'react-i18next'; const useHookTranslation = () => { - const appLang = localStorage.getItem('ui_lang'); + const appLang = localStorage.getItem('ui_lang') || 'en'; const identifier = LANGUAGE_LIST.find((record) => record.locale === appLang)?.identifier || From bf56c1e98bef8573f661d7a3f114fe563db780e5 Mon Sep 17 00:00:00 2001 From: rhahao <26148770+rhahao@users.noreply.github.com> Date: Sun, 8 Dec 2024 15:23:53 +0300 Subject: [PATCH 16/16] chore(demo): add first report value for persons --- src/utils/dev.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/utils/dev.ts b/src/utils/dev.ts index 8e14eba938..6a40266d75 100644 --- a/src/utils/dev.ts +++ b/src/utils/dev.ts @@ -225,6 +225,11 @@ export const importDummyPersons = async (showLoading?: boolean) => { } if (femaleStatus === 'unbaptized') { + person.person_data.first_report = { + value: startDateTemp, + updatedAt: new Date().toISOString(), + }; + person.person_data.publisher_unbaptized = { active: { value: true, updatedAt: new Date().toISOString() }, history: [ @@ -275,6 +280,11 @@ export const importDummyPersons = async (showLoading?: boolean) => { ) ); + person.person_data.first_report = { + value: startDateTemp, + updatedAt: new Date().toISOString(), + }; + person.person_data.publisher_baptized = { active: { value: true, updatedAt: new Date().toISOString() }, baptism_date: {